Developing a city builder terrain engine

I am attempting to develop a terrain engine for a city builder game and I am running into a lot of conceptual issues. I’ve read the manual, searched the forums to figure out the best way to do this. Based on my conversation with Craig in IRC last night I have a feeling I am getting a lot of things wrong, so I am asking for help on getting me down the right path. In return I will write a tutorial on this :slight_smile:

My objective is to texture the terrain in a similar manner to sim city 4, a base texture, beach and snow texture based on elevation, and a rock texture based on slope. Because it is a city builder I have no idea what the mesh will look like. All the tutorials I found the terrain was a premade mesh. I am using GeoMipTerrain.

Currently my plan is to use texture splatting. Is this the best method to use for my situation? Do I use a 5th alpha texture with each RGBA channel for each terrain? I read the multitexture pages and it looks like decal mode is the best. I also don’t know how to connect the texture to the alpha.

I’ve been told and read shaders are also awesome at this. I read the shader manual but I still do not know how to relate it to textures.

Thanks again for any help.

GeoMips are based on heightmaps – commonly just a grayscale image that encodes the terrain height in its values. So, the terrain is “pre-generated” inso much as that the mesh itself is built from the image. However, there is no reason why you cannot modify the image in realtime and then rebuilt the terrain mesh based on that.

Splatting is the one method used to paint a terrain mesh with a texture. There are others, but splatting usually goes hand-in-hand with the heightmap concept and is simplest to implement. In fact, if you use a program like L3DT, you can have it generate both the heightmap and the splat-maps for you, automagically.

You don’t need shaders to do this, as it can be done with good ole standard texture combiners. There are several threads on here that go into detail about how to do this, either using shaders or multitexturing. Sorry, no links, didn’t save any bookmarks, but I remember the guy who write the GeoMip terrain code (rdb) also posted a code snippet showing how to set up the texture combiners for splatting.

I managed to get terrain splatting using a color texture plus four terrain textures. I am trying to add a sixth texture to use as a grid. I am at a total loss on what I am doing wrong for adding the 6th texture.


colorTexture = Texture()
        # Textureize
        grassTexture = loader.loadTexture("Textures/grass.png")
        rockTexture = loader.loadTexture("Textures/rock.jpg")
        sandTexture = loader.loadTexture("Textures/sand.jpg")
        snowTexture = loader.loadTexture("Textures/ice.png")
        # Grid for city placement and guide and stuff
        gridTexture = loader.loadTexture("Textures/grid.png")
        # Set multi texture
        # Source
        root.setTexture( TextureStage('color'),colorTexture ) 
        root.setTexture( TextureStage('rock'),rockTexture )
        root.setTexture( TextureStage('grass'),grassTexture ) 
        root.setTexture( TextureStage('sand'), sandTexture) 
        root.setTexture( TextureStage('snow'), snowTexture ) 
        root.setTexture( TextureStage('grid'), gridTexture ) 


//Cg profile arbvp1 arbfp1


void vshader( in float4 vtx_position  : POSITION,
              in float2 vtx_texcoord0 : TEXCOORD0,
              in uniform float4x4 mat_modelproj,
              out float4 l_position  : POSITION,
              out float2 l_texcoord0 : TEXCOORD0,
              out float2 l_detail1   : TEXCOORD1,
              out float2 l_detail2   : TEXCOORD2,
              out float2 l_detail3   : TEXCOORD3,
              out float2 l_detail4   : TEXCOORD4,
              out float2 l_detail5   : TEXCOORD5)
     l_position = mul(mat_modelproj,vtx_position);
     l_texcoord0 = vtx_texcoord0;
    //detail texture coordinates scaled, we must get the correct scale fator to make terrain look like in EarthSculptor
     l_detail1 = vtx_texcoord0;   //* 27.365000 in EarthSculptor
     l_detail2 = vtx_texcoord0;  //*20.000000 in EarthSculptor
     l_detail3 = vtx_texcoord0;  //*22.389999 in EarthSculptor
     l_detail4 = vtx_texcoord0;  //*22.389999 in EarthSculptor
     l_detail5 = vtx_texcoord0; //Tried 0.0001 to 10000, no dice

void fshader( in float4 l_position  : POSITION,
              in float2 l_texcoord0 : TEXCOORD0,
              in float2 l_detail1   : TEXCOORD1,
              in float2 l_detail2   : TEXCOORD2,
              in float2 l_detail3   : TEXCOORD3,
              in float2 l_detail4   : TEXCOORD4,
              in float2 l_detail5   : TEXCOORD5,
              in uniform sampler2D tex_0 : TEXUNIT0,
              in uniform sampler2D tex_1 : TEXUNIT1,
              in uniform sampler2D tex_2 : TEXUNIT2,
              in uniform sampler2D tex_3 : TEXUNIT3,
              in uniform sampler2D tex_4 : TEXUNIT4,
              in uniform sampler2D tex_5 : TEXUNIT5,

              out float4 o_color : COLOR )
    //add 4 detail colors
    float4 alpha = tex2D(tex_0, l_texcoord0.xy);
    o_color = tex2D(tex_1, l_detail1.xy) * alpha.x
            + tex2D(tex_2, l_detail2.xy) * alpha.y
            + tex2D(tex_3, l_detail3.xy) * alpha.z
            + tex2D(tex_4, l_detail4.xy) * alpha.w;
    o_color = o_color + tex2D(tex_5, l_detail5.xy);
    o_color.a = 1.0;

Thank you for any help.

I have used texture splatting before but found the texture sampling artefacts to sux. Also it probably could be made to work.

I would recommend just using PNM images and painting your terrain on it. Repaint and regenerate when it changes both hight and colour.

Er, you didn’t assign any sort value to your TextureStage’s. That is a big mistake - they could end up in an arbitrary order. Call setSort on every TextureStage after creating it.

I thought I post a quick update on my discovery. Somehow the greyscale is also being expressed as alpha. A black grid is showing up as invisible but a white grid is very visible. I have a greyscale png image so I am still trying to figure out why this is the case.

I don’t think panda3d reads gray scale png’s properly.

Of course it does.

croxis: are you sure it is a greyscale-only image, not a greyscale-alpha one?

Aye. I waited to get the cvs build from the cvs build bot in the hope that it might of been a panda bug that was fixed. Nope, it is my stupidity :smiley:

I’m now using a jpg for the grid just for the sake of simplicity and to make 100% sure there is no alpha channel.

The black circle in the middle is just for effect. However the result is this:

My hunch is that the line in my shader

o_color = o_color + tex2D(tex_5, l_detail5.xy); 

is causing this effect.

I made a small sample with the snippit of code, textures, and shaders which I put here:

Try putting at the end of your shader:

o_color.a = 1.0;

Is it just me, or is the problem we’re seeing due to the use of additive color, and not related to alpha at all?

If you start with a color that is mostly green, and add a new color to it that is either black or white, you’ll end up with a color that is green wherever the added color was black, and white where the added color was white. This is because black is 0 and white is 1, and x + 0 == x, but x + 1 == 1 (since the colors clamp at 1).


Ah! You are right. This indeed looks like the behavior because o_color seems to be white initially.
Something like this would work:

o_color = tex2D(tex_5, l_detail5.xy);

Or even:

o_color *= tex2D(tex_5, l_detail5.xy);

o_color *= tex2D(tex_5, l_detail5.xy); did the trick :smiley:
I understand David’s explanation on adding color, but what I am not understanding is how multiplication is producing this result. Could someone indulge me or point me to some readings?

Also, to make sure I know what is going on, tex_5 is the texture and l_detail.xy is the pixel coordinate for that texture?

At the points where the detail texture is white (value of 1), the output is the texture multiplied by 1 (meaning it shows up), where the detail texture is black (value of 0) the output is the texture multiplied by 0, meaning it won’t show up.

Right. A texture coordinate of 0 means the upperleft corner of the texture, 1 the lowerright corner. If you exceed the value, it repeats in your case (you can change this behavior by changing the wrap mode of the texture).

Short version: I suck at writing shaders and want to use the auto shader. Is using texture combine mods appropriate for spatting 4+ textures onto a terrain?

I am trying a different approach using the autoshader and texture combine modes (writing my own is difficult for me, I just don’t “get it” yet). I can reproduce the terrain demo (1 mapping texture with rgb + a and the empty space is used by the third texture), however I have one additional texture which I would like to use.

I don’t see support in the texture combine modes for using rgba channels, just the combined rgb and the alpha. I’m going to need 2 different textures (rgb + alpha), however I am having trouble adding the second map in. It likes to override the textures underneeth.

        grass_alpha_ts = TextureStage("grassalphamaps")

        grassTexture = loader.loadTexture("Textures/grass.png")
        grassTS = TextureStage('grass')

        rock_alpha_ts = TextureStage("rockalphamaps")

        rockTexture = loader.loadTexture("Textures/rock.jpg")
        rockTS = TextureStage('rock')
        rockTS.setCombineRgb(TextureStage.CMAdd, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor)
        # I tried all modes, all of them influence the previous layer (grass)

        root.setTexture(grass_alpha_ts, grass_texture_map)

        root.setTexture( grassTS, grassTexture )
        root.setTexScale(grassTS, size, size) 

        root.setTexture(rock_alpha_ts, rock_texture_map)
        # Works as expected tot his point

        root.setTexture( rockTS, rockTexture ) 
        root.setTexScale(rockTS, size, size) 

I’m having exact the same problem.
Also started with shaders and want to give Autoshaders a try

The Problem:
A second SetSavedResult(True) seems to affect previous stages who should already be calculated/drawn at this time. Its not possible to use two alpha layers with this restriction/bug

I tested a simple case with 3 Texures, one base texture(ground) and 2 textures(grass/rocks) on top of that with different alpha textures.

draw ground: sort(40)
draw alpha_grass: sort(0) -> SetSavedResult(True)
draw grass: sort(50) -> Interpolate with previous and saved
// Works so far
draw alpha_rocks: sort(10)
// Still no problem:
// but if i call -> SetSavedResult(True) now, even if i dont draw any rock texture, the grass texture disappears and i see just my ground texture.

The funny thing is that if i turn AutoShaders off it behaves in a different way, then my grass texture is visible but with the alpha of my rock texture :smiley:

Another thing: The order of the SavedResults doesn’t seem to matter, but the sorting of the stage. LastSavedResult doesnt seem to take the last saved but the one saved with the topmost sorting

If anyone has a working example for AutoShaders with more than 4 textures please let us know.

Anyone? Did you figure it out skywalker? I’ve been searching, reading, trying stuff… I can’t figure it out.

My understanding is there is no autoshader if you are using custom shaders.

I’m not using custom shaders. I’m a complete noob with this stuff and for now I want to learn/use as much built-in stuff as possible.

That’s been the base I have been working off. As I understand it from looking at the code and playing around, it applies the first texture to everything (what would be the texture for alpha1 from l3dt), then using alpha maps replaces textures 2 and 3 (splatting). Finally it takes the full map texture and blends it with everything.

The issue is I cannot figure out how to use splatting/alpha maps for additional textures. Lets say I want to splat a sandy beach, grassy areas, rocky areas, snow, dirt paths, and stone roads. I cannot figure this out. In fact, I haven’t been able to break this line into two different steps:

root.setTexture(ts, loader.loadTexture(“l3dt/terrain_Alpha_2.png”, “l3dt/terrain_Alpha_3.png”))

To look at it another way, lets say I have an existing terrain with some mixture of full map textures and alpha mapped platted textures. What do I need to do to add another alpha mapped texture, possibly at a later time, or is this even possible?

edit: getMaxTextureStages() returns 4. Does this mean there is no way for me to apply more than 4 textures, either splatted or blended, to the entire GeoMipTerrain? It still doesn’t explain why I can’t break out the alpha loadTexture into 2 separate lines.