Deceit in Depth

While the perspective version looks great, there remains one problem. Move the time around until the rotating grey sphere ducks underneath the ground.

Figure 13.6. Bad Intersection

Bad Intersection

Hmm. Even though we've made it look like a mathematically perfect sphere, it does not act like one to the depth buffer. As far as it is concerned, it's just a circle (remember: discard prevents depth writes and tests as well).

Is that the end for our impostors? Hardly.

Part of the fragment shader's output is a depth value. If you do not write one, then OpenGL will happily use gl_FragCoord.z as the depth output from the fragment shader. This value will be depth tested against the current depth value and, if the test passes, written to the depth buffer.

But we do have the ability to write a depth value ourselves. To see how this is done, load up the tutorial (using the same code again) and press the H key. This will cause all impostors to use depth-correct shaders.

Figure 13.7. Depth Correct Impostor

Depth Correct Impostor

This shader is identical to the ray traced version, except for these lines in the fragment shader:

Example 13.4. Depth Correct Fragment Shader

Impostor(cameraPos, cameraNormal);
//Set the depth based on the new cameraPos.
vec4 clipPos = cameraToClipMatrix * vec4(cameraPos, 1.0);
float ndcDepth = clipPos.z / clipPos.w;
gl_FragDepth = ((gl_DepthRange.diff * ndcDepth) +
    gl_DepthRange.near + gl_DepthRange.far) / 2.0;

Basically, we go through the process OpenGL normally goes through to compute the depth. We just do it on the camera-space position we computed with the ray tracing function. The position is transformed to clip space. The perspective division happens, transforming to normalized device coordinate (NDC) space. The depth range function is applied, forcing the [-1, 1] range in the fragment shader to the range that the user provided with glDepthRange.

We write the final depth to a built-in output variable gl_FragDepth.