September 11
Converting Displacement Maps into Normal Maps in HLSL
Working on my VTF tutorial, I came across the need to apply lighting to a terrain. For lighting, we need normals, of course, but I only had heightmaps available.
When the terrain is static, this poses no problems at all. All we have to de is generate a normal map from the heightmaps with whatever tools we want. For example, NVIDIA has a Photoshop plugin for this here.
However, when we have dynamic terrain, with real time deformation, this is no longer an option. This is especially the case when we make the deformations on the displacement map. After lots of reading and searching, I've come across the Sobel Filter, and a sample from ATI that uses it to compute normal maps.
As a result, I made a pixel shader that takes as input the displacement map and outputs the normal map. I believe this could be used for static terrain, and computed in a Content Processor, but I'm not very experienced with Content Processors, so I didn't try it.
At runtime, after modifying the displacement map, I apply this shader and put the result into a render target. This result is then used when drawing the terrain, when computing lighting in the pixel shader.
So here's the HLSL code:
float textureSize = 256.0f;
float texelSize = 1.0f / textureSize ; //size of one texel;
float normalStrength = 8;
float4 ComputeNormalsPS(in float2 uv:TEXCOORD0) : COLOR
{ float tl = abs(tex2D (displacementSampler, uv + texelSize * float2(-1, -1)).x); // top left
float l = abs(tex2D (displacementSampler, uv + texelSize * float2(-1, 0)).x); // left
float bl = abs(tex2D (displacementSampler, uv + texelSize * float2(-1, 1)).x); // bottom left
float t = abs(tex2D (displacementSampler, uv + texelSize * float2( 0, -1)).x); // top
float b = abs(tex2D (displacementSampler, uv + texelSize * float2( 0, 1)).x); // bottom
float tr = abs(tex2D (displacementSampler, uv + texelSize * float2( 1, -1)).x); // top right
float r = abs(tex2D (displacementSampler, uv + texelSize * float2( 1, 0)).x); // right
float br = abs(tex2D (displacementSampler, uv + texelSize * float2( 1, 1)).x); // bottom right
// Compute dx using Sobel:
// -1 0 1
// -2 0 2
// -1 0 1
float dX = tr + 2*r + br -tl - 2*l - bl;
// Compute dy using Sobel:
// -1 -2 -1
// 0 0 0
// 1 2 1
float dY = bl + 2*b + br -tl - 2*t - tr;
// Build the normalized normal
float4 N = float4(normalize(float3(dX, 1.0f / normalStrength, dY)), 1.0f);
//convert (-1.0 , 1.0) to (0.0 , 1.0), if needed
return N * 0.5f + 0.5f;
}
You should tweak normalStrength until the result you get is satisfactory.
And here is the result of applying that code to two heightmaps, and some terrain lit using a normal map generated by this shader.
Until next time, Happy Coding!