Game Development Community

Bumpy Atlas Terrain... here's my latest attempt...

by Ves · in Torque Game Engine Advanced · 09/03/2007 (10:46 pm) · 52 replies

Well it was a huge pain, and I had to bump some shaders to 3.0 to get this to work (though I really think that's a mistake and it probably will all work in 2.0 shaders). I extended the base atlas shader (the clipmap pass shader) to include the bumpmapping. I had to move the lightmapping from the blended image cache section to this clipmap section as well. This is gross and no where near as elegant as I would like it but I did get it to work.

I also had to modify the atlas vertex format as well, as I needed the tangent on a vertex basis. I actually calculated the Binomial in the shader like everyone suggests but I think that might be the source of some the funky artifacts I get. I didn't modify atlas output yet as I am just verifying if this will work right now. So for the interum there is a stutter on loading a clipmap section as it calculates the tangents for all of the verticies at that point (thanks to some barrowed and modified code from the tmesh section).

Anyway here are some screens...

This is a great example of why you SHOULDN'T bump map everything in sight
i5.photobucket.com/albums/y153/vesuvias/whenbumpmaps_gowrong.jpg

high specular but it does move with the sun and player position
i5.photobucket.com/albums/y153/vesuvias/highspec_bump.jpg

Way too exagerated a bump map but its a good example bump
i5.photobucket.com/albums/y153/vesuvias/bumpy2.jpg

I actually like this one... specular is too high but its snow :)
i5.photobucket.com/albums/y153/vesuvias/bumpy1.jpg

The shaders I am using were adapted from mr noolness in this thread www.garagegames.com/mg/forums/result.thread.php?qt=36211, I am just using the standard normal mapping shader but I am pretty sure I can get the relief mapping one to work.

I set everything up so that I could followup with some code that actually uses different bump maps based on the terrian opacity map. So that will be my next goal. I may end up rewriting how terrains are blended though. I need to think on this somemore. I would really rather the opacity and lightmap be the only textures that are runtime merged via the clipmapping functions. Shaders should take care of the individual blending and bumpmapping I think. I may take a step even further back and try to get that to work. when I clean this up and if no one else has done a better one I will post a resource on what I did. Till then, comments and suggestions are welcome.

Ves

**Edited to replace images with image links... to save page loads....
Page «Previous 1 2 3 Last »
#1
09/04/2007 (12:11 am)
Nice. Just need to get a fog calculation in there to break up the grid lines. I wouldn't recommend doing relief mapping. I did it back on Legacy terrain a while ago. It ends up being more trouble than its worth and ultimately a huge performance killer. Instead, try using parallax mapping. That will greatly improve it visually and it's pretty cheap to do.

A few things to consider:

Only use a normal map and specular algorithm, don't copy the code from the Nool shader pack. There's no reason why it shouldn't run on shader model 2, you just might need to clean up some of the code and only use what's really necessary.

What kind of artifacts are you getting?

I noticed the one in the last image, but that's only because Atlas uses clip maps.
#2
09/04/2007 (7:48 am)
Very nice. Keep posting your progress.
#3
09/04/2007 (8:52 am)
I would like to see a vid of this. :)

Looking gewd.
#4
09/04/2007 (10:30 pm)
Thanks for the comments. I redid the code that fetched the lightmap image for me as I was pulling the wrong texture. I was pulling a stracth texture as opposed to actually constructing the lightmap the correct way. I am now doing it correctly so the lightmaps are correct now.

I am still getting artifacts in the terrian and so I looked at the code where I add the Tangent to the atlas verticies. It turns out that the tangent was coming out to either 1 Z or 1 Y. which in turn produces artifacts like this:
i5.photobucket.com/albums/y153/vesuvias/artifactissue.jpg

A mapping the Tangent variable gives a better understanding of what I mean:
i5.photobucket.com/albums/y153/vesuvias/artifactTmap.jpg

Looking further into that code it looked like the normals were not coming out right either (they were either 0xyz or 1z). So I recalculated those as well and it improved things slightly but its still way off. I am now begining to believe that it may be a result of large slope variations combined with a lack of detail in the terrain geometry. The data and code may actually be correct and that just how that terrian bumps.

I went ahead and took Matt's advice and tried out the parralex shader. I also added in a lerp to fade out the normal at a distance. Below is the result:
i5.photobucket.com/albums/y153/vesuvias/parrlexwshortfade.jpg
this one uses the a correct bump (heightmap in the alpha) for parralex:
i5.photobucket.com/albums/y153/vesuvias/parralexwlongfade.jpg

I am going to try and clean up the shader a bit and work on changeing the bumpmaps with the opacity texture on the blend. That way the rock would have a different bumpmap from the snow. I really want to see the effect that produces...

Ves

**Edited to replace images with image links... to save page loads....
#5
09/05/2007 (4:38 pm)
That would give a very nice look if you could get different bumps on the different terrains, no doubt.
#6
09/05/2007 (5:00 pm)
You could always do it through a blend map. Heck, you could even use the opacity map if you wanted to.
#7
09/05/2007 (7:14 pm)
Yeah I am going to blend it when everything else gets blended in imagecache section. Each blended image will just have a corresponding bumpmap. That was really what I was shooting for anyway.

You can't really blend a normal map but I think I can choose the normal map based on the greatest opacity value. Or guess I could do three lerps (ie lerp(lerp(r,b), lerp(g,a)) ). Thought thats not really accurate either. It's an interesting problem. Hmmm...

After I get bump blending done I will post the final main shader for critique and suggestions. It's ugly and I could use any input to help optimise it.

Ves
#8
09/05/2007 (7:36 pm)
Normal maps can blend well depending on what equation you use. Take the legacy terrain shader for example.
This equation is really brilliant and worked well with normal mapped terrains.
OUT.col  = (o.r * t1 + o.g * t2 + o.b * t3 + o.a * t4);

Where o is the blend map and t1-4 are the different samplers.
#9
09/06/2007 (8:31 am)
I thought that the potential to unormalize that blended normal would mess things up more than it did (ie you fully blend to normals (1,0,0) and (0,1,0) which results in (1,1,0) which is no longer normal length). Hmm but now that I think about it I guess I could normalise the result (not sure why I didn't think of that before).

In any case I did get it to work :)... I do however think I broke the parralex portion (can't really vary by heightmap on a blended normal). Currently I am doing the parrlex portion before the blend (so yes I am doing that bit 4 times in the shader). I am not so sure that's worth the result as I am having trouble telling the difference between parralex and a regular normal shader after the blend for some reason. I probably borked up the code though. Anyway here is the first fully bumpmapped blended shot. I am kind of proud of this one.
i5.photobucket.com/albums/y153/vesuvias/blendedBump1.jpg

The performance by the way is horible. But I can't tell if its the shader or the C++ torque code at this point. It is a monster shader for sure but I am also executing 3!!!! full renders (on a flat 2 triangle plane) on every clipmap rectangle update. This will have a huge impact on final fillrate. This is horrible but I have to get the diffuse, lightmap and opacity maps all independant of each other (so I can pass them in as seperate textures to my monster shader that brings all of this togeather). This monster shader reached the texture register limit last night as I am now passing a texture in for all 16 of the avialable registers for shader V 2.0. Thats :
4 diffuse textures (one per clipmap level)
4 lightmap textures (one per climpmap level)
4 opacity textures (one per clipmap level)
4 Bumpmap textures (one per terrian type)

I am leaning towards streamlinging the lightmap and opacity maps in some way. Theres no reason to clipmap those using the current method as I really need all of that data to get a correct image (that stuff is more data than it is visual imagery so lower res versions at a distance I don't think helps us as much). Having those images always fully available on given clipmap saves me not only the extra two renders I had but it will save me cycles in the final shader since I won't have to blend thier clipmap levels like I do with the diffuse.

anyway here is the final clipmap pixel shader in all its slow unoptimised glory....
FragOut pixClipmap( PixClipMapConnectData IN, 
				   uniform sampler2D diffuseMap[mapCount] : register(S0),
				   uniform sampler2D lightMap[mapCount] : register(S4),
				   uniform sampler2D opacityMap[mapCount] : register(S8),
				   uniform sampler2D bumpMap1 : register(S12),
				   uniform sampler2D bumpMap2 : register(S13),
				   uniform sampler2D bumpMap3 : register(S14),
				   uniform sampler2D bumpMap4 : register(S15),
				   uniform float4    ambient   : register(PC_AMBIENT_COLOR),
				   uniform float4    specular  : register(PC_MAT_SPECCOLOR),
				   uniform float     shine     : register(PC_MAT_SPECPOWER)
				   )
{

   FragOut OUT;

   const float3 diffuse  = {1,1,1};

   // view and light directions
   float3 v = normalize(IN.eyepos);
   float3 l = normalize(IN.lightvec);

   // Do a layered blend into accumulator, so most detail when we have it will show through...
   float4 color = tex2D(diffuseMap[0], IN.texCoord[0].xy);
   float4 lm = tex2D(lightMap[0], IN.texCoord[0].xy);
   float4 oMap = tex2D(opacityMap[0], IN.texCoord[0].xy);
   
   	
   
   //Clipmap level merging
   //woohoo!!! why to one lerp when you can do 3... per level... every pixel
   for(int i=1; i<mapCount; i++)
   {
      float  scaleFactor = saturate(IN.fade[i] * 2.0);
      float4 layer       = tex2D(diffuseMap[i], IN.texCoord[i].xy);
      color = lerp(layer, color , scaleFactor);
      float4 layer2       = tex2D(lightMap[i], IN.texCoord[i].xy);
      lm = lerp(layer2, lm , scaleFactor);
      float4 layer3       = tex2D(opacityMap[i], IN.texCoord[i].xy);
      oMap = lerp(layer3, oMap , scaleFactor);	 
   }
   
   //Begin ugly parralex calculation block I have to do this 4 times since 
   //I need to do it preblend
   float2 uv = IN.texCoord[0].xy*128.0;
   float height = tex2D(bumpMap1,uv).w * 0.06 - 0.03;
   float2 uv1 = uv;
   uv1 += height * v.xy;
   float4 t1 = tex2D(bumpMap1,uv1);
   height = tex2D(bumpMap2,uv).w * 0.06 - 0.03;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t2 = tex2D(bumpMap2,uv1);
   height = tex2D(bumpMap3,uv).w * 0.06 - 0.03;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t3 = tex2D(bumpMap3,uv1);
   height = tex2D(bumpMap4,uv).w * 0.06 - 0.03;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t4 = tex2D(bumpMap4,uv1);

   //and finally a blended normal map
   float4 normal = (oMap.r * t1 + oMap.g * t2 + oMap.b * t3 + oMap.a * t4); 

   //fade normal at a distance
   float s = saturate((IN.dis.x-50)/300);
   
   // normal map
   normal.xyz=normalize(normal.xyz-0.5); 
   normal.y=-normal.y;

   normal.xyz = lerp(normal.xyz,float3(1,0,0),s);
   

   // compute diffuse and specular terms
   float4 lightVec = lit(dot(l,normal.xyz),dot(normalize(l-v),normal.xyz),shine);
   
   // attenuation factor
   float att=1.0-max(0,l.z); att=1.0-att*att;

   // compute final color
   float4 finalcolor;	
   finalcolor.xyz = ambient*color.xyz + att*((color.xyz*diffuse*lightVec.y) + lightVec.z ) * lm; 
   finalcolor.w=1.0;
    
   OUT.col = finalcolor;
	

   return OUT;
}

Ves

**Edited to replace images with image links... to save page loads....
#10
09/06/2007 (3:46 pm)
The results are excellent Ves. I hope you get the performance issues worked out, this has massive potential.
#11
09/06/2007 (3:59 pm)
I dunno how the performance is, but it definitely looks hella cool.
#12
09/08/2007 (7:31 am)
One last shot and I will stop bugging you guys :)....

I ended up completely circumventing the clipmap stuff altogeather (for textures). I now load the alpha, lightmap, all the textures and the bumpmaps independant of the clipmaper. That part was actually a lot easier than I thought. I simply added some variables (just like the detail texture) to the AtlasInstance2 class. This isn't a clipmapped texture so you won't be able to have a 32k x 32k alpha blended texture but for now I think thats ok. I do want to get the alpha and lightmaps back into the clipmapper at some point as it would be awesome to be able to page those files like that (and make ginormous alpha a clipmaps). Performance is better now (I am not doing the clipmap combines or rendering the clipmap textures on everymove) but still a bit slow.

I am still getting the artifacts described above but I am 80% sure thats a vertex normal issue. I am working on loading the normal map that comes out of L3DT. Thats probably a smoother more accurate normal map than the one I end up generating a loadtime as it is based on the entire hieghtmap not just the verticies we derived when generated our atlas terrian.

I also fixed the shader to do parralex shading correctly (the code above was borked like I suspected). It now looks much cooler.

Anyway I created a desert map in L3DT. I carved me a road into the terrian and everything :). enjoy...
i5.photobucket.com/albums/y153/vesuvias/desertshot_blended.jpg
Ves
#13
09/08/2007 (8:00 am)
Awesome work. I worked a bit on optimizing your shader, though unfortunately I don't have any media to test it with. If you're interested, contact me. I'm sure we could get this running on 2.0 hardware in no time.
#14
09/08/2007 (9:19 am)
Really, I insist keep bugging us. this is nice work.
#15
09/08/2007 (9:36 am)
That looks great! I hope you share all of it ;)
#16
09/08/2007 (4:54 pm)
Great work! Cant wait to get a running computer with shaders, just a couple days. I'm really interested to see how you do the final blend calculations. Are you using the atlas blend-map or an external one? I was try to something similar without bump-mapping and stuff at first, but my blend map took up an extra TeX slot. Excellent work though!!

Also you said it was still about slow? Is it doing this rendering over the entire Atlas terrain, with this detail? perhaps you can add a fading for the effect like what was implemented in the detail texture? Just my 2 pennies;)...er three.
#17
09/08/2007 (7:49 pm)
Will you be releasing all the code for us to use? :D
#18
09/08/2007 (10:37 pm)
Absolutely, I will be releaseing this. I definatly need to clean it up first. I wanted to add the normal map first as I still have artifacts in the terrain from the Tangents being all screwy. I really wanted to take a step back and do things the right way (by making an entirely seperate atlas map type that implements the IAtlasClipMapImageCache interface). That might be pushing it but we will see.

@Matt: Thanks for the offer on shader help. I definatly need any pointers ringing every last cycle out of my shaders.

Here is that latest Vertex and Pixel shaders I am using, They've changed a bit and I did correct the parrlex portion as well as eliminate nearly all of the clipmap stuff (This may not be a wise decision in the long run as I really do want a massive clipmapped opacity map and a large clipmapped lightmap).

VertClipMapConnectData vertClipmap( VertData IN,
                  uniform float4x4 modelView         : register(C0),
                  uniform float    morphT            : register(C49),
                  uniform float4   mapInfo[mapCount] : register(C50),
				  uniform float3   lightpos        : register(VC_LIGHT_DIR1),
				  uniform float3   eyePos          : register(VC_EYE_POS),
				  uniform float4x4 objTrans        : register(VC_OBJ_TRANS)
                  )
{
   VertClipMapConnectData OUT;

   // Do vertex transform...
   OUT.hpos = atlasVertex(IN, modelView, morphT);

   OUT.baseCoord = IN.texCoord;
   // And get our morphed texcoord...
   float2 tc = atlasTexCoord(IN, morphT);

   // Scale texcoords.
   for(int i=0; i<mapCount; i++)
      OUT.texCoord[i] = tc * mapInfo[i].z;

   // Caculate fades. We never fade base level, so skip it.
   float4 distAcc = 0;
   for(int i=1; i<mapCount; i++)
      distAcc[i] = distance(mapInfo[i].xy, OUT.texCoord[i].xy);

   // Do all the fade biasing in one go.
   for(int i=0; i<4; i++)
      OUT.fade[i] = (distAcc[i] * (2.0 * fadeConstant) - (fadeConstant - 1.0)) / 2.0;

   // tangent vectors in view space 
	float3x3 tangentspace = float3x3(IN.T, cross( IN.T, IN.normal) , IN.normal);
	
  // view in tangent space
	float3 obvv = eyePos - IN.position;
    float3 eye = mul( tangentspace, obvv );
    eye = -eye;
	OUT.outeyeposition = eye;
	
	OUT.lightvec.xyz = -lightpos;
	OUT.lightvec.xyz = mul(tangentspace, OUT.lightvec);	
	OUT.lightvec = (OUT.lightvec / 2.0) + 0.5;
	OUT.dis = 0;
    OUT.dis.x = distance(eye, OUT.hpos.xyz);
	//OUT.lightvec = IN.T;

   return OUT;
}

FragOut pixClipmap( PixClipMapConnectData IN, 
				   //uniform sampler2D diffuseMap[mapCount] : register(S0),
				   uniform sampler2D diffuseMap1 : register(S0),
				   uniform sampler2D diffuseMap2 : register(S1),
				   uniform sampler2D diffuseMap3 : register(S2),
				   uniform sampler2D diffuseMap4 : register(S3),
				   uniform sampler2D lightMap[mapCount] : register(S4),
				   uniform sampler2D opacityMap[mapCount] : register(S8),
				   uniform sampler2D bumpMap1 : register(S12),
				   uniform sampler2D bumpMap2 : register(S13),
				   uniform sampler2D bumpMap3 : register(S14),
				   uniform sampler2D bumpMap4 : register(S15),
				   uniform float4    ambient   : register(PC_AMBIENT_COLOR),
				   uniform float4    specular  : register(PC_MAT_SPECCOLOR),
				   uniform float     shine     : register(PC_MAT_SPECPOWER)
				   )
{

   FragOut OUT;

   const float3 diffuse  = {1,1,1};
   // Do a layered blend into accumulator, so most detail when we have it will show through...
   //float4 color = tex2D(diffuseMap[0], IN.texCoord[0].xy);
   //float4 lm = tex2D(lightMap[0], IN.texCoord[0].xy);
   float4 lm = tex2D(lightMap[0], IN.baseCoord.xy);
   //float4 oMap = tex2D(opacityMap[0], IN.texCoord[0].xy);
   float4 oMap = tex2D(opacityMap[0], IN.baseCoord.xy);
   // view and light directions
   float3 v = normalize(IN.eyepos);
   float3 l = normalize(IN.lightvec);
	
   float2 uv = IN.baseCoord.xy*128.0;


   /*for(int i=1; i<mapCount; i++)
   {
      float  scaleFactor = saturate(IN.fade[i] * 2.0);
      float4 layer       = tex2D(diffuseMap[i], IN.texCoord[i].xy);
      color = lerp(layer, color , scaleFactor);
	  //float4 layer2       = tex2D(lightMap[i], IN.texCoord[i].xy);
      //lm = lerp(layer2, lm , scaleFactor);
	  //float4 layer3       = tex2D(opacityMap[i], IN.texCoord[i].xy);
      //float4 layer3       = tex2D(opacityMap[i], IN.baseCoord.xy);
      //oMap = lerp(layer3, oMap , scaleFactor);
	  //OUT.col += (i/mapCount)*0.25;
   }*/
	

   
   
   float height = tex2D(bumpMap1,uv).w * 0.02 - 0.01;
   float2 uv1 = uv;
   uv1 += height * v.xy;   
   float4 t1 = tex2D(diffuseMap1,uv1);

   height = tex2D(bumpMap2,uv).w * 0.02 - 0.01;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t2 = tex2D(diffuseMap2,uv1);

   height = tex2D(bumpMap3,uv).w * 0.02 - 0.01;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t3 = tex2D(diffuseMap3,uv1);

   height = tex2D(bumpMap4,uv).w * 0.02 - 0.01;
   uv1 = uv;
   uv1 += height * v.xy;
   float4 t4 = tex2D(diffuseMap4,uv1);
   
   float4 color = (oMap.r * t1 + oMap.g * t2 + oMap.b * t3 + oMap.a * t4); 

   t1 = tex2D(bumpMap1,uv1);
   t2 = tex2D(bumpMap2,uv1);
   t3 = tex2D(bumpMap3,uv1);
   t4 = tex2D(bumpMap4,uv1);
   float4 normal = (oMap.r * t1 + oMap.g * t2 + oMap.b * t3 + oMap.a * t4);
   // parallax code
   //float height = tex2D(bumpMap[0],uv).w * 0.06 - 0.03;
   //uv += height * v.xy;

   // normal map
   normal.xyz=normalize(normal.xyz-0.5); 
   normal.y=-normal.y;

   //normal fade
   /*float s = saturate((IN.dis.x-100)/100);
   normal.xyz = lerp(normal.xyz,float3(1,0,0),s);*/
   

   // compute diffuse and specular terms
   float4 lightVec = lit(dot(l,normal.xyz),dot(normalize(l-v),normal.xyz),shine);
   
   // attenuation factor
   float att=1.0-max(0,l.z); att=1.0-att*att;
   
    // compute final color
    float4 finalcolor;
    finalcolor.xyz = ambient*color.xyz + att*((color.xyz*diffuse*lightVec.y) + lightVec.z ) * lm;
    finalcolor.w=1.0;
    //finalcolor.xyz = lightVec.y;
    OUT.col = finalcolor;
	
   return OUT;
}

Never let it be said that I am shy about posting ugly incorrect code. I already see where I am still messing up the parralex mapping. I will correct that and repost at some point. I will try and keep this updated as I progress toward an actual resource for this stuff.

Ves
#19
09/09/2007 (4:18 am)
That last screenshot looks amazing. =)
#20
09/09/2007 (8:57 am)
Here is another shot (I know I promised not to... but someone said it was ok :). Since I am configuring the textures and bumps in the mission file, its cake to swap those in and out (no more recompiling the atlas). This will lend itself nicely to seasonal changes I think....
i5.photobucket.com/albums/y153/vesuvias/Moreshots1.jpg
I really think using the L3DT generated lightmap has huge advantages. That light map is generated with some built in "bumpiness" based on the terrian types you use in that program. If you look to the right in that shot the light shading on the ground is a result of that process. That part of the map was configured to be sandy, and therefore had some sand dune like lightmap "rdiges" baked right into the lightmap. Very, VERY cool, stuff. That product is way better than I think people realise.

Using that lightmap of course creates issues when we want to use a Torque genrated lightmap. But I think (and I will need to look at that lightmap code) that it should be possible for torque to ignore the terrain lightmap generation and render DTS/DIFF shadows directly to a render target texture. If thats the case than we should be able to have L3DT generate the lightmaps and have torque bake the building shadows on top of that.

That's been added to my list of really cool things I need to further research. Other things are on that list too, such as changing the shader operation (bump map, parralex map, relief map, no map, etc) on the vertex level based on a custom built color/opacity map, blending more than 4 textures on the map, etc.

Thre performance is greatly improved and it seems that a lot of my previous performance woes had more to do with running under the visual studio debugger than anything else. When booted up outside the debugger I wasn't seeing any stuttering at all. Good stuff, this is very useable.

Below is an interesting issue with parralex shaders I think. Check out the right and left side of this road as it cuts through the hill here. Notice that in full shadow parralex is completely flat and blurry( left side). On the right side the angle we are looking at the triangle from I think totally messes up that heightmap calculation. It reveals the illusion for what it really is :(....
i5.photobucket.com/albums/y153/vesuvias/Parralex-issues.jpg
Back to work.... i will see if can get some rough code out to everyone soon... I would love to see what map makers that actually have talent could do with this :)...

Ves
Page «Previous 1 2 3 Last »