How do i texture terrain using the vertex normal?

Posted By: Caermundh

How do i texture terrain using the vertex normal? - 11/13/12 18:19

Hi!

I am attempting to write a multi-texture terrain shader that will apply either a rock texture or a grass texture based on the normal of the current vertex in the terrain geometry. If the vertex is "flat" (or near flat) with respect to the world space, apply the grass texture to it, if it is not flat (IE a cliff or wall) apply the rock texture to it.

As you can see from the attached screenshot, what i have is close but not quite. Im still getting grass halfway up the walls in many areas and in some areas im getting rock where the surface is flat enough it should have grass.

Screenshot: [url=http://s1126.beta.photobucket.com/user/caermundh/media/terrain_screenshot.png.html#/user/caermundh/media/terrain_screenshot.png.html?&_suid=135283034745306911133684460592][/url]

Here is the shader code i am using:

Code:
MATERIAL* drakanterrianmat = 
{   skin1 = rock_bmap;
    skin2 = grass_bmap;
    effect = "const float4x4 matWorldViewProj;
              const float4x4 matWorld;
              const float4 vecAmbient;
              const float4 vecSunDir;
              
              float4 AmbientColor = float4(1, 1, 1, 1);
              float AmbientIntensity = 1.0f;
              float DiffuseIntensity = 1.0f;
              float4 SunColor = {0.9f,0.9f,0.5f,1.0f};
              
              texture entSkin1;
              texture mtlSkin1;
              texture mtlSkin2;

              sampler ColorMapSampler=sampler_state
              {   Texture = <entSkin1>;
                  AddressU = Clamp;
                  AddressV = Clamp;
              };

              sampler RockMapSampler=sampler_state
              {   Texture = <mtlSkin1>;
                  AddressU = Wrap;
                  AddressV = Wrap;
              };
              
              sampler GrassMapSampler=sampler_state
              {   Texture = <mtlSkin2>;
                  AddressU = Wrap;
                  AddressV = Wrap;
              };

              struct VS_Input
              {   float4 InPos      :POSITION;
                  float3 InNormal   :NORMAL;
                  float2 InTex      :TEXCOORD0;
              };
              
              struct VS_Output
              {   float4 OutPos    :POSITION;
                  float2 OutTex    :TEXCOORD0;
                  float3 OutNormal :TEXCOORD1;
                  float2 OutPMTex  :TEXCOORD2;
              };
              
              struct PS_Input
              {   float2 InTex    :TEXCOORD0;
                  float3 InNormal :TEXCOORD1;
                  float2 InPMTex  :TEXCOORD2;
              };

              VS_Output vs(VS_Input input)
              {   VS_Output output;
                  output.OutPos=mul(input.InPos,matWorldViewProj);
                  output.OutNormal=normalize(mul(input.InNormal,matWorld));
                  output.OutTex = input.InTex*512;
                  output.OutPMTex = input.InTex;
                  return output;
              }

              float4 ps(PS_Input input) : COLOR0
              {   float3 Normal = normalize(input.InNormal);
                  float Orientation = dot(float3(0,1,0),Normal);
                  float4 Color = tex2D(ColorMapSampler,input.InPMTex);
                  float4 Ambient = AmbientIntensity*vecAmbient;
                  float4 Diffuse = DiffuseIntensity*saturate(dot(vecSunDir,Normal));
                  float4 ReturnTexture;
                  Diffuse *= SunColor;
                  if(Orientation>0.75)
                  {   ReturnTexture = tex2D(GrassMapSampler, input.InTex);
                  }
                  else
                  {   ReturnTexture = tex2D(RockMapSampler, input.InTex);
                  }
                  return (Ambient+Diffuse)*ReturnTexture;
              }
              
              technique ApplyTextures
              {   pass P0
                  {   VertexShader = compile vs_2_0 vs();
                      PixelShader = compile ps_2_0 ps();
                  }
              }";
}



To be honest im more than a bit fuzzy on how exatly normals work to begin with. Can someone tell me how to fix this shader so it does what its supposed to do?
Posted By: Kartoffel

Re: How do i texture terrain using the vertex normal? - 11/13/12 19:17

I can try to fix it but it would be a great help if you upload a very small test-project
which includes a terrain file, the shader, the textures and a main script to start it.
Posted By: Superku

Re: How do i texture terrain using the vertex normal? - 11/13/12 21:45

Basically, it should be enough to only use InNormal.y in the vertex shader and pass this value to the pixel shader. There you take this value to blend (/ lerp) between the grass and rock texture.
Posted By: Caermundh

Re: How do i texture terrain using the vertex normal? - 11/13/12 22:11

@Kartoffel:

I can put together a test level for you pretty easy, but how do I upload it to this site?

@Superku:

I basically am using InNormal.y....wait...you say use it in the Vertex shader instead of the Pixel shader? Ill give that a try and let you know what changes.
Posted By: Kartoffel

Re: How do i texture terrain using the vertex normal? - 11/13/12 22:19

I thought about triplanar texture mapping which should give pretty good results.

about the test file:
I don't think you can upload it here in the forum but you can upload it to a file hoster (like mediafire).
Posted By: Caermundh

Re: How do i texture terrain using the vertex normal? - 11/13/12 23:42

Ok, heres the modified shader:

Code:
MATERIAL* drakanterrianmat = 
{   skin1 = rock_bmap;
    skin2 = grass_bmap;
    effect = "const float4x4 matWorldViewProj;
              const float4x4 matWorld;
              const float4 vecAmbient;
              const float4 vecSunDir;
              
              float4 AmbientColor = float4(1, 1, 1, 1);
              float AmbientIntensity = 1.0f;
              float DiffuseIntensity = 1.0f;
              float4 SunColor = {0.9f,0.9f,0.5f,1.0f};
              float Normal_y;
              
              texture entSkin1;
              texture mtlSkin1;
              texture mtlSkin2;

              sampler ColorMapSampler=sampler_state
              {   Texture = <entSkin1>;
                  AddressU = Clamp;
                  AddressV = Clamp;
              };

              sampler RockMapSampler=sampler_state
              {   Texture = <mtlSkin1>;
                  AddressU = Wrap;
                  AddressV = Wrap;
              };
              
              sampler GrassMapSampler=sampler_state
              {   Texture = <mtlSkin2>;
                  AddressU = Wrap;
                  AddressV = Wrap;
              };

              struct VS_Input
              {   float4 InPosition   :POSITION;
                  float3 InNormal     :NORMAL;
                  float2 InTexture    :TEXCOORD0;
              };

              struct VS_Output
              {   float4 OutPosition :POSITION;
                  float2 OutTexture  :TEXCOORD0;
                  float3 OutNormal   :TEXCOORD1;
                  float2 OutBlendTex :TEXCOORD2;
              };
              
              struct PS_Input
              {   float2 InTexture   :TEXCOORD0;
                  float2 InNormal    :TEXCOORD1;
                  float3 InBlendTex  :TEXCOORD2;
              };
              
              
              VS_Output vs(VS_Input Input)
              {   VS_Output Output;
                  Output.OutPosition=mul(Input.InPosition,matWorldViewProj);
                  Output.OutNormal=normalize(mul(Input.InNormal,matWorld));
                  Output.OutTexture = Input.InTexture*512;
                  Output.OutBlendTex = Input.InTexture;
                  Normal_y = Input.InNormal.y;
                  return Output;
              }

              float4 ps(PS_Input Input) : COLOR0
              {   float4 Ambient = AmbientIntensity*vecAmbient;
                  float4 Diffuse = DiffuseIntensity*saturate(dot(vecSunDir,normalize(Input.InNormal)));
                  float4 Color = tex2D(ColorMapSampler,Input.InBlendTex);
                  float4 GrassTexture = tex2D(GrassMapSampler, Input.InTexture);
                  float4 RockTexture = tex2D(RockMapSampler, Input.InTexture);
                  float4 ReturnTexture = lerp(RockTexture,GrassTexture,Normal_y);
                  Diffuse *= SunColor;
                  return (Ambient+Diffuse)*ReturnTexture;
              }
              
              technique ApplyTextures
              {   pass P0
                  {   VertexShader = compile vs_2_0 vs();
                      PixelShader = compile ps_2_0 ps();
                  }
              }";
}



This does not work at all. If i use "float4 ReturnTexture = lerp(RockTexture,GrassTexture,Input.InNormal.y);" instead, i still have grass climbing halfway up the walls.
Posted By: Superku

Re: How do i texture terrain using the vertex normal? - 11/14/12 00:20

Well as you need the normal anyway for the diffuse part you don't have to pass it separately to the PS, sorry for the misunderstanding.
Something like
float fac = saturate(Input.InNormal.y*9); //control the sharpness of the blend with the factor ("9")
float4 ReturnTexture = lerp(RockTexture,GrassTexture,fac);
should do the trick (if I understood your issue correctly). Of course you have to tweak it a little here and there.
Posted By: Caermundh

Re: How do i texture terrain using the vertex normal? - 11/14/12 15:58

OK,

Tried that. Lerp makes the grass and stone textures blend much more nicely, but it still has grass halfway up the walls.
Posted By: Caermundh

Re: How do i texture terrain using the vertex normal? - 11/14/12 16:24

@Kartoffel:

uploaded a test level to mediafire:

http://www.mediafire.com/?7kysisresg7anvs

You can move the camera around with the arrow keys plus the left and right mouse buttons:

Left Mouse: move camera foward
Right Mouse: move camera backwards
Up Arrow: move camera up
Down Arrow: move camera down
Left arrow: strafe camera left
right arrow: strafe camera right
Posted By: Superku

Re: How do i texture terrain using the vertex normal? - 11/14/12 17:36

float fac = saturate(saturate(Input.InNormal.y-0.8)*5);
float4 ReturnTexture = lerp(RockTexture,GrassTexture,fac);
Posted By: Caermundh

Re: How do i texture terrain using the vertex normal? - 11/14/12 18:27

It doesnt matter what I change fac to. If fac is less than about .75, i get rock everywhere, if its greater than about 2, i get grass everywhere. If its between those values, i get varying blend strengths of grass and rock, but regaurdless of the strength, i still have grass halfway up the walls.

I need something more than Input.InNormal.y - this alone is obviously not enough information for the shader to correctly determine the normal of a given pixel. Its very close, but not quite.

The problem seems to lie with the border vertexes. Those vertex that are right where the geometry goes from mostly horizontal to mostly vertical. Its like the normal information for these is being returned incorrectly, or maybe my shader isnt reading it correctly.

Maybe i need to pass the normal from the vertex shader to the pixel shader as a COLOR instead of a TEXCOORD? I read in a couple places that people who were having problems getting a shader to work properly were able to fix the problem by changing the semantic of some one variable or other.

What other ways are there for there to be a disconnect between the reported normal in the shader and the visual normal on the screen?
Posted By: Superku

Re: How do i texture terrain using the vertex normal? - 11/14/12 19:02

As you obviously didn't care to test my suggestion, I didn't read the rest of your post, sorry.
© 2024 lite-C Forums