A different shader approach to TerraMonkey

Hi guys,



I’m working right now on some jME3 terrain stuff and what I understood so far concerning texturing is the following:

We have a texture-map A.K.A. alpha-map that holds the splatting information for at maximum 4 different textures. Every color channel in the texture map holds the amount of blending for every texture. While this is good concerning shading passes, it limits the possibilities considerably.



I’m right now trying to achieve a shading which takes into account more than 4 textures ( somewhere near 10). Now what I want is to have smooth transitions between two textures and not really depending on height information (which was the case in jME2), but rather on tile/ground type information.



What I want to recreate is especially some kind of Total War world map .

I just love the overall appearance of their terrain.



They use a height map to build the terrain and furthermore a tile map to give each quad a “ground type”. Every ground type has its own texture like “coast”,“land”, “mountain high” or “mountain low”.



The height map:





The ground type map:





Building the terrain to recreate the geometry from above is no problem. But the texturing, oh my…



Now my (dumb&naive) idea would be to pass 10 texture coordinates and 10 textures into the shader and do some kind of color blending. But that is just pathetic and (maybe!?) not even GLSL conform. So are there any ideas for an intelligent way to do this? Especially for a big amount of different textures?



Thx for reading :slight_smile:

Its “ok” to do that, only thing you should think about that most graphics cards only support 8 texture pipelines, still you can use more textures on the cost of fps. I’d strongly think about what you can do in photoshop before adding the texture to the game. Also dont think you can use one big texture for your whole terrain, textures shouldnt be bigger than 1024x1024 for most cards, 2048 can be considered “high quality”.

Hi normen,



thanks for the reply. Yes, I understand that there is limit for texture pipelines and also a limit concerning texture size. And this is exactly the reason I want to avoid going my naive way. Total War also uses another “climate map” for climate zones, which then again indicates which tileset to choose from for specific parts of the terrain (i.e. “land” is a different texture in Europe than in Northern Africa). So in total, they render their terrain with about 20 textures at the same time!!!



There must be a performant way to do it, although I cannot really understand how to accomplish this.



For every elementary quad (1x1) in the terrain, I know whether it has the type “coast” or “land” or whatever. Let’s assume we have an atlas texture which holds all tiles. Then I could set the uv-coordinates to choose the specific tile out of it. But there wouldn’t be a smooth transition from adjacent tiles. Instead there would be a sudden graphical jump to the other tile type. So, not possible this way…

I was working on a very similar project to what you’re suggesting (different game engine) but the principal the same.

this was how i made a shader with more than 3 splattable textures



–first you need more than one “Texture Key” 2 can easily get you 10 textures

– to get more, you should break up your terrain into regions “like climates in mtw2” each region has its own texture set with one or two similar textures to overlap with other regions ex…“Steppe” texture available in northern and desert regions for a seamless transition.

— you use all the RGB values of the first texture with the RGB of the second to get 9 textures plus a default texture (all blank)





here’s a part of my shader for my project from PANDA3d…its a different engine but the principal the same


void fshader( in float4 l_position : POSITION,
in float2 l_texcoord0 : TEXCOORD0,
in float2 l_texcoord3 : TEXCOORD3,
in float l_brightness,
in uniform sampler2D tex_0 : TEXUNIT0,
in uniform sampler2D tex_1 : TEXUNIT1,
in uniform sampler2D tex_2 : TEXUNIT2,

in uniform sampler2D tex_3 : TEXUNIT3, //River
in uniform sampler2D tex_4 : TEXUNIT4, //Beach
in uniform sampler2D tex_5 : TEXUNIT5, //Swamp
in uniform sampler2D tex_6 : TEXUNIT6, //Grass
in uniform sampler2D tex_7 : k_arid, //Arid
in uniform sampler2D tex_8 : k_desert, //Dersert
in uniform sampler2D tex_9 : k_rolling, //RollingHills
in uniform sampler2D tex_10 : k_arolling, //AridRollinigHills
in uniform sampler2D tex_11 : k_hilly, //Hilly
in uniform sampler2D tex_12 : k_ahilly, //Aridhilly
in uniform sampler2D tex_13 : k_mountian, //mountians
in uniform sampler2D tex_14 : k_alpine, //alpine

out float4 o_color : COLOR )
{
float4 key0=tex2D(tex_0,l_texcoord3);
float4 key1=tex2D(tex_1,l_texcoord3);
float4 key2=tex2D(tex_2,l_texcoord3);

float4 river= tex2D(tex_3,l_texcoord0);
float4 tundra= tex2D(tex_4,l_texcoord0);
float4 swamp= tex2D(tex_5,l_texcoord0);
float4 grass= tex2D(tex_6,l_texcoord0);
float4 arid= tex2D(tex_7,l_texcoord0);
float4 desert= tex2D(tex_8,l_texcoord0);

float4 rhills= tex2D(tex_9,l_texcoord0);
float4 arhills= tex2D(tex_10,l_texcoord0);
float4 hilly= tex2D(tex_11,l_texcoord0);
float4 ahilly= tex2D(tex_12,l_texcoord0);
float4 mount= tex2D(tex_13,l_texcoord0);
float4 alpine= tex2D(tex_14,l_texcoord0);

o_color+=river*(key0.r * 1-key2.r);
o_color+=tundra*(key0.g * 1-key2.g);
o_color+=swamp*(key0.b * 1-key2.b);
o_color+=grass*(key0.r * key2.r);
o_color+=arid*(key0.g * key2.g);
o_color+=desert*(key0.b * key2.b);

o_color+=rhills*(key1.r * 1-key2.r);
o_color+=arhills*(key1.g * 1-key2.g);
o_color+=hilly*(key1.b * 1-key2.b);
o_color+=ahilly*(key1.r * key2.r);
o_color+=mount*(key1.g * key2.g);
o_color+=alpine*(key1.b * key2.b);


o_color=o_color*((l_brightness));
o_color.a=1.0;



}


not a valid jme3 shader but shouldn't be to hard to rework
performance wise i have a gt280
I get 500+ fps with large textures 1024x1024 unless i zoomed out viewing the whole continent.....i was trying to make the whole world...lol

i hope this helps
2 Likes

Hi crabbitz,



wow, thanks for the explanation and your suggestions concerning climate region overlapping. It’s a good idea! :slight_smile:

Do you have some screenshots of your project? Would really like to see something.



I see that you still have to push all those textures into the shader…hmm…ok. At least you only have one pair of tex-coordinates.



One question remains: You mentioned that you could produce 9 + 1 textures from two RGB textures. When I use two key textures I sum it up to 8 (2RGBA channels). With three keys, I get 16 textures (when using the alpha channel aswell). The same I see in your shader. Could you elaborate on how you get 9+1?



In addition, your shader does an interesting thing in weighting the color

[java]

o_color+=river
(key0.r * 1-key2.r);

o_color+=tundra*(key0.g * 1-key2.g);

o_color+=swamp*(key0.b * 1-key2.b);

o_color+=grass*(key0.r * key2.r);

o_color+=arid*(key0.g * key2.g);

o_color+=desert*(key0.b * key2.b);

[/java]



Does it mean, that in order to modify a tile from “river” to “grass”, I first select it by setting a full value to the red channel of the 0th key texture and slide it in the shader with the red channel of the 2nd key texture? That’s neat…

1)

Imo we does not need 255 degrees of how much terrain is present.



So we could put more terains into terrain texture (alpha map).



But then whole tile would have exactly same terrain mix, instead of gradual change in subtile level.



Is there some mistake in my thinking? Would it be acceptable trade off?





2)

How many different terrains in one terrain zone are needed to make it nice?

Imo each edge needs 2 terrains in common with neighbour. Probubly et least 1 terrain is present a lot in big area, and it should not be too hard to make do with 3 other terrains on edges, and terrain block should have something dominant that make it different, so 1 or 2 more are imo needed.

So my guess is, that around 6 terrains per block should be enough to crate seamlessly looking world.

normen said:
Its "ok" to do that, only thing you should think about that most graphics cards only support 8 texture pipelines, still you can use more textures on the cost of fps. I'd strongly think about what you can do in photoshop before adding the texture to the game. Also dont think you can use one big texture for your whole terrain, textures shouldnt be bigger than 1024x1024 for most cards, 2048 can be considered "high quality".


Actually all(even intel onboard, I know none that is not capable) cards that are able to run jme3 are alsoa ble to handle 4048 size.

Yeah, they can display them, but I doubt you get great performance :wink:

Hardly depends not tat much on the texture size, limiting is the throughput from the texture units. It makes nearly no difference if you use a few large or many smaller ones. (At least as far as I know/understand it)

Ok, I guess its the mainly the mem swapping making stuff slow then, because it definitely is slower than 1024 textures :slight_smile:

Dodikles said:
Hi crabbitz,

wow, thanks for the explanation and your suggestions concerning climate region overlapping. It's a good idea! :)
Do you have some screenshots of your project? Would really like to see something.

I see that you still have to push all those textures into the shader...hmm...ok. At least you only have one pair of tex-coordinates.

One question remains: You mentioned that you could produce 9 + 1 textures from two RGB textures. When I use two key textures I sum it up to 8 (2*RGBA channels). With three keys, I get 16 textures (when using the alpha channel aswell). The same I see in your shader. Could you elaborate on how you get 9+1?

In addition, your shader does an interesting thing in weighting the color
[java]
o_color+=river*(key0.r * 1-key2.r);
o_color+=tundra*(key0.g * 1-key2.g);
o_color+=swamp*(key0.b * 1-key2.b);
o_color+=grass*(key0.r * key2.r);
o_color+=arid*(key0.g * key2.g);
o_color+=desert*(key0.b * key2.b);
[/java]

Does it mean, that in order to modify a tile from "river" to "grass", I first select it by setting a full value to the red channel of the 0th key texture and slide it in the shader with the red channel of the 2nd key texture? That's neat...


how i get 9+1.....
default is all black both RGBAs
then i use a combination of each RGB of the second key with the first
example:

Red:Red = beach
o_color+=beach*(key0.r * key2.r); make sure both key1,key2 red are present
Red:Green = river
Red:Blue = grassland

Green:Red = crops
Green:Green = hills
Green:Blue =mountains

Blue:Red = tundra
Blue:Green = desert
Blue:Blue =glacier

and theoretically you can do MORE like
Red:( all black on key2)
another texture o_color+=newtexture*(key0.r * 1-(key2.r * key2.g * key2.b )); 1 minus all key2 insures it is black
Green:( all black on key2)
""
BLUE:( all black on key2)
""
that gets you up to 13, and 14 could be all white on both keys o_color+=newtexture*((key1.r * key1.g * key1.b ) * (key2.r * key2.g * key2.b ))

these textures MUST go first though, cause if you have a RED:RED combo it would paint this texture and that texture, putting this first will paint this texture if there is a RED on key2 then it gets over painted with the correct texture

i don't like using the alpha channel cause makes life difficult for me in photoshop when i was making the landscape

I believe the limit would be 14 textures in all, i think a shader can only handle 16 textures....2 which must be keys

@Alpedar -- 0 -255... I've tried this....due to the way texture values work in a shaders it doesn't work like you think, the 0-255 value should only be used for opacity.....see in a shader if one pixel is 255,0,0 and the next pixel is 0,0,255 the area between the is something like 155,0,155 and this gives you weird results if you use the 0-255 for different textures

if i wasn't so busy with college classes I'd do a conversion demo cause I'm going to need a texture splatting shader for my own jme3 project sooner or later :(

promise not to laugh here’s a pic of the game i was “trying” to make…proof of concept







Uploaded with ImageShack.us

I think thats exactly how a proof of concept game has to look :wink:

Even if minimalization and magnificalization are set to nearest neighbour and no mip map?

In my proof of concept I could even choose specific color to represent given terrain (but then shader would look like big if and there would be no gradual blending.

Yes, I think enabling the “fluff” shold be done as a very last step in game develoment, when you have the subsystems working in a way that you can really gauge their performance and adjust the visuals accordingly. Thats why you see games coming out for console with certain filters etc. disabled because these were sacrificed for e.g. AI performance. You will never get real data out of test cases where you try to combine all effects or lots of geometry objects to gauge performance, its simply not a real world test.

Hi monkeys,



so I made a first draft and am quite satisfied with the result. Please don’t laugh about the shitty textures but I needed to steal my way through the internet :smiley:



So first an overview over the Europe map und then a close up on Sicilly and Italy :slight_smile:











I push the maximum of 16 textures into the fragment shader. Two serve as keys which I computed out of the ground_type image and the other 14 are real textures. Another good thing is that I only use the standard texcoords of the terrain to accomplish this effect. By the way: Is the sampler limit of 16 in the frag shader a hardware based limitation or jME3’s harsh rule? But I guess that 14 textures have to suffice when not using different climate zones.



There’s still a lot of refinement to do concerning better textures, smoother terrain etc. but I am not a friend of parameter tweaking, so I let it be like this. Another thing is lighting I need to incorporate. I’ll look into the example for this.



Is there an interest in the shader and material code? It’s straightforward really but if so, I’ll post it here.

2 Likes

Great work! Sharing code is always very welcome :smiley:

Thanks, normen! Ok, so here the material definition:

[java]

MaterialDef Terrain {



MaterialParameters {



Texture2D Key0

Texture2D Key1

Texture2D Tex0

Texture2D Tex1

Texture2D Tex2

Texture2D Tex3

Texture2D Tex4

Texture2D Tex5

Texture2D Tex6

Texture2D Tex7

Texture2D Tex8

Texture2D Tex9

Texture2D Tex10

Texture2D Tex11

Texture2D Tex12

Texture2D Tex13





}



Technique {

VertexShader GLSL100: materials/terrain/terrain.vert

FragmentShader GLSL100: materials/terrain/terrain.frag



WorldParameters {

WorldViewProjectionMatrix

}





}



Technique FixedFunc {

}



}

[/java]



Nothing special, just all the texture declarations. The vertex shader is the same from the example code, so unnecessary to post here.



And now the frag shader:

[java]

uniform sampler2D m_Key0;

uniform sampler2D m_Key1;

uniform sampler2D m_Tex0;

uniform sampler2D m_Tex1;

uniform sampler2D m_Tex2;

uniform sampler2D m_Tex3;

uniform sampler2D m_Tex4;

uniform sampler2D m_Tex5;

uniform sampler2D m_Tex6;

uniform sampler2D m_Tex7;

uniform sampler2D m_Tex8;

uniform sampler2D m_Tex9;

uniform sampler2D m_Tex10;

uniform sampler2D m_Tex11;

uniform sampler2D m_Tex12;

uniform sampler2D m_Tex13;



varying vec2 texCoord;





void main(void)

{



vec4 outColor;

vec4 key0=texture2D(m_Key0,texCoord);

vec4 key1=texture2D(m_Key1,texCoord);



vec4 t0=texture2D(m_Tex0,texCoord16.0);

vec4 t1=texture2D(m_Tex1,texCoord
16.0);

vec4 t2=texture2D(m_Tex2,texCoord16.0);

vec4 t3=texture2D(m_Tex3,texCoord
16.0);

vec4 t4=texture2D(m_Tex4,texCoord16.0);

vec4 t5=texture2D(m_Tex5,texCoord
16.0);

vec4 t6=texture2D(m_Tex6,texCoord16.0);

vec4 t7=texture2D(m_Tex7,texCoord
16.0);

vec4 t8=texture2D(m_Tex8,texCoord16.0);

vec4 t9=texture2D(m_Tex9,texCoord
16.0);

vec4 t10=texture2D(m_Tex10,texCoord16.0);

vec4 t11=texture2D(m_Tex11,texCoord
16.0);

vec4 t12=texture2D(m_Tex12,texCoord16.0);

vec4 t13=texture2D(m_Tex13,texCoord
16.0);









outColor=t0*(key0.r * key1.r);

outColor+=t1*(key0.g * key1.r);

outColor+=t2*(key0.b * key1.r);

outColor+=t3*(key0.a * key1.r);



outColor+=t4*(key0.r * key1.g);

outColor+=t5*(key0.g * key1.g);

outColor+=t6*(key0.b * key1.g);

outColor+=t7*(key0.a * key1.g);



outColor+=t8*(key0.r * key1.b);

outColor+=t9*(key0.g * key1.b);

outColor+=t10*(key0.b * key1.b);

outColor+=t11*(key0.a * key1.b);



outColor+=t12*(key0.r * key1.a);

outColor+=t13*(key0.g *key1.a);







outColor = 0.7;

outColor.a=1.0;



gl_FragColor = outColor;

}



[/java]

First just reading in all texels and multiply by 16 to scale it a bit (looks nicer…not really :P). From there its simple multiplications of the key components. With two RGBA key textures one can do this splatting for 4
4=16 textures, but the frag shader gets angry with 18 samplers… Why is that? At the end I multiply it with 0.7 as a sorry excuse for no lighting.



I also wanted to show you the key textures but I didn’t find a fast way to dump a Texture2D :stuck_out_tongue:

Alpedar said:
Even if minimalization and magnificalization are set to nearest neighbour and no mip map?
In my proof of concept I could even choose specific color to represent given terrain (but then shader would look like big if and there would be no gradual blending.


In that situation(nearest neighbor) yeah that would be the ideal solution, 1 key, 15 textures, but if he wants a mtw2 type map then he's going to need some blending, thus using all 0-255
I did some experimenting with 0-150 being a texture and 150-255 being at texture and so on, had mixed results, but I wanted blending, if he doesn't want any blending then color specific texturing the way to go.

Alpedar pursues a kind of “real” tile based map like in some JRPG games or whatever, as far as I understand it. I want to have (as crabbitz noted) a terrain to look as similiar as possible to the Medieval 2 screenshot. I even think that the guys from Creative Assembly did a comparable approach although they stuff 15 textures onto their terrain. Maybe they have a more elaborate blending technique that works with only one key texture.



@crabbitz: And concerning the hassle with alpha channels. The CA guys did a good thing with this ground_type map because you can infer the right values for the key textures just by looking at a pixel in the ground type. I do the same here:

[java]



//base is the current base index in the RGBA buffer

// data0 and data1 are the buffers for key 0 and key 1 accordingly.

// BTW this is the Total War ground_type ->key conversion code

// Beach 0

if (r == 255 && g == 255 && b == 255) {

data0[base + 0] = (byte) 255;

data1[base + 0] = (byte) 255;

} // Fertile high 1

else if (r == 101 && g == 124 && b == 0) {

data0[base + 1] = (byte) 255;

data1[base + 0] = (byte) 255;

} // Fertile low 2

else if (r == 0 && g == 128 && b == 128) {

data0[base + 2] = (byte) 255;

data1[base + 0] = (byte) 255;

} // Fertile medium 3

else if (r == 96 && g == 160 && b == 64) {

data0[base + 3] = (byte) 255;

data1[base + 0] = (byte) 255;

} // Forest dense 4

else if (r == 0 && g == 64 && b == 0) {

data0[base + 0] = (byte) 255;

data1[base + 1] = (byte) 255;

} // Forest sparse 5

else if (r == 0 && g == 128 && b == 0) {

data0[base + 1] = (byte) 255;

data1[base + 1] = (byte) 255;

} // Hills 6

else if (r == 128 && g == 128 && b == 64) {

data0[base + 2] = (byte) 255;

data1[base + 1] = (byte) 255;

} // Impassable 7

else if (r == 64 && g == 64 && b == 64) {

data0[base + 3] = (byte) 255;

data1[base + 1] = (byte) 255;

} // Mountains high 8

else if (r == 196 && g == 128 && b == 128) {

data0[base + 0] = (byte) 255;

data1[base + 2] = (byte) 255;

} // Mountains low 9

else if (r == 98 && g == 65 && b == 65) {

data0[base + 1] = (byte) 255;

data1[base + 2] = (byte) 255;

} // Ocean?!?!?!?! 10

else if (r == 64 && g == 0 && b == 0) {

data0[base + 2] = (byte) 255;

data1[base + 2] = (byte) 255;

} // Sea deep 11

else if (r == 128 && g == 0 && b == 0) {

data0[base + 3] = (byte) 255;

data1[base + 2] = (byte) 255;

} // Sea shallow 12

else if (r == 196 && g == 0 && b == 0) {

data0[base + 0] = (byte) 255;

data1[base + 3] = (byte) 255;

} // Swamp 13

else if (r == 0 && g == 255 && b == 128) {

data0[base + 1] = (byte) 255;

data1[base + 3] = (byte) 255;

} // Wilderness

//WARNING: THIS TILE IS NOT IN THE SHADER BECAUSE > 16 2d_sampler

else if (r == 0 && g == 0 && b == 0) {

//data0[base + 2] = (byte) 255;

//data1[base + 3] = (byte) 255;

data0[base + 3] = (byte) 255;

data1[base + 1] = (byte) 255;



}

[/java]