Depth Clamping

That's all well and good, but this:

This is never a good thing. Sure, it keeps the hardware from dividing by zero, which I guess is important, but it looks really bad. It's showing the inside of an object that has no insides. Plus, you can also see that it has no backside (since we're doing face culling); you can see right through to the object behind it.

If computer graphics is an elaborate illusion, then clipping utterly shatters this illusion. It's a big, giant hole that screams, this is fake! as loud as possible to the user. What can we do about this?

The most common technique is to simply not allow it. That is, know how close objects are getting to the near clipping plane (ie: the camera) and do not let them get close enough to clip.

And while this can function as a solution, it is not exactly good. It limits what you can do with objects and so forth.

A more reasonable mechanism is depth clamping. What this does is turn off camera near/far plane clipping altogether. Instead, the depth for these fragments are clamped to the [-1, 1] range in NDC space.

We can see this in the Depth Clamping tutorial. This tutorial is identical to the vertex clipping one, except that the keyboard function has changed as follows:

Example 5.12. Depth Clamping On/Off

void keyboard(unsigned char key, int x, int y)
{
    static bool bDepthClampingActive = false;
    switch (key)
    {
    case 27:
        glutLeaveMainLoop();
        break;
    case 32:
        if(bDepthClampingActive)
            glDisable(GL_DEPTH_CLAMP);
        else
            glEnable(GL_DEPTH_CLAMP);
        
        bDepthClampingActive = !bDepthClampingActive;
        break;
    }
}

When you press the space bar (ASCII code 32), the code will toggle depth clamping, with the glEnable/glDisable(GL_DEPTH_CLAMP) calls. It will start with depth clamping off, since that is the OpenGL default.

When you run the tutorial, you will see what we saw in the last one; pressing the space bar shows this:

Figure 5.8. Depth Clamping

Depth Clamping

This looks correct; it appears as if all of our problems are solved.

Appearances can be deceiving. Let's see what happens if you move the other object forward, so that the two intersect like in the earlier part of the tutorial:

Figure 5.9. Depth Clamp With Overlap

Depth Clamp With Overlap

Oops. Part of it looks right, just not the part where the depth is being clamped. What's going on?

Well, recall what depth clamping does; it makes fragment depth values outside of the range be clamped to within the range. So depth values smaller than depth zNear become depth zNear, and values larger than depth zFar become depth zFar.

Therefore, when you go to render the second object, some of the clamped fragments from the first are there. So the incoming fragment from the new object has a depth of 0, and some of the values from the depth buffer also have a depth of 0. Since our depth test is GL_LESS, the incoming 0 is not less than the depth buffer's 0, so the part of the second object does not get rendered. This is pretty much the opposite of where we started: previous rendered objects are in front of newer ones. We could change it to GL_LEQUAL, but that only gets us to exactly where we started.

So a word of warning: be careful with depth clamping when you have overlapping objects near the planes. Similar problems happen with the far plane, though backface culling can be a help in some cases.

Note

We defined depth clamping as, in part, turning off clipping against the camera near and far planes. If you're wondering what happens when you have depth clamping, which turns off clipping, and a clip-space W <= 0, it's simple. In camera space, near and far clipping is represented as turning a pyramid into a frustum: cutting off the top and bottom. If near/far clipping is not active, then the frustum becomes a pyramid. The other 4 clipping planes are still fully in effect. Clip-space vertices with a W of less than 0 are all outside of the boundary of any of the other four clipping planes.

The only clip-space point with a W of 0 that is within this volume is the homogeneous origin point: (0, 0, 0, 0); everything else will be clipped. And a triangle made from three positions that all are at the same position would have no area; it would therefore generate no fragments anyway. It can be safely eliminated before the perspective divide.

Fork me on GitHub