Depth Map, Normal Map
A lot of interesting graphics techniques require the use of depth maps and normals maps. Depth maps store information of how far a pixel is from the eye in the final rendered scene. Similarly, normal maps store the surface’s normal vector for the cooresponding pixel. One way to do this would be to draw the scene with vertex normals instead of vertex colors and copy the color and depth buffers into a texture. A more straight forward technique uses vertex and pixel shaders and gives us more control over the final maps. Also, if we use GL_EXT_framebuffer_object, we can avoid any nasty context switches that are associated with pBuffers.
The first thing we do is understand the layout of the maps. Really, we’ll only be rendering to a single texture. This has the advantage of saving space, but may cause a problem if we only have 8 bits per channel. If the normal {x, y, z} is stored in {r, g, b} and the depth stored in {a}, then we’ve gone from 32 bit floating point {x} to 8 bit {r}, or 24 bit depth to 8 bit {a}; the same goes for the other components. This loss in precision will show up in banding artifacts and the errors will be carried through for every computation that follows that map use. But sometimes this is unavoidable based on the platform that we’re using.
How do we fit a normal and depth into a single pixel? Well, we know which color component will hold which value, but we then find out that the GL will clamp our texture values to [0,1]. No negative values for us. Luckily, the depth is already clamped to [0,1] by virtue of the graphics pipeline; the normal is an entirely different problem. It does have an easy solution though. Normals can be normalized (duh), which means each component will map to [-1,1]. If we normalize, then multiply by two and subtract one, our mapping will be fine. We just need to undo the tranformation when we reference it later on.
This gives us our GLSL vertex shader:
varying float depth;
void main()
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
vec4 eyeTmp = gl_Position;
eyeTmp.xyz = eyeTmp.xyz / eyeTmp.w;
depth = eyeTmp.z;
}
And our fragment shader:
varying float depth;
void main()
{
vec3 N = normalize(normal);
N = 0.5 * N + 0.5;
gl_FragColor = vec4(N, depth);
}
Now, that may not be the absolute best way, but it works on nearly every platform that can use GLSL. One issue that I should bring up, is that the pixel’s depth value can be directly referenced in the fragment shader through gl_FragCoord.z; on some machines, reding this value was very, very slow, but now I’ve seen great improvement in reading the value. This means we can remove all references to the varying variable depth from both shaders.
Depending on your system, you may have acces to floating point textures through GL_ARB_texture_float. Two very nice things about that extension. First, floating point textures are not clamped by the GL. This means that messing around with the normals can be avoided. Second, we are no longer limited to 8 bits per channel. We now have access to 16 or 32 bits per channel which will reduce or remove banding issues completly.
Now what do our shaders look like? The vertex shader:
void main(void) {
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
}
Fragment shader:
void main (void) {
gl_FragColor = vec4(normalize(normal), gl_FragCoord.z);
}