Depth Buffer Precision

From OpenGL Wiki
Revision as of 18:51, 30 April 2006 by Marco (talk | contribs)
Jump to: navigation, search

Depth buffering seems to work, but polygons seem to bleed through polygons that are in front of them. What's going on?

You may have configured your zNear and zFar clipping planes in a way that severely limits your depth buffer precision. Generally, this is caused by a zNear clipping plane value that's too close to 0.0. As the zNear clipping plane is set increasingly closer to 0.0, the effective precision of the depth buffer decreases dramatically. Moving the zFar clipping plane further away from the eye always has a negative impact on depth buffer precision, but it's not one as dramatic as moving the zNear clipping plane.

The OpenGL Reference Manual description for glFrustum() relates depth precision to the zNear and zFar clipping planes by saying that roughly log2(zFar/zNear) bits of precision are lost. Clearly, as zNear approaches zero, this equation approaches infinity.

While the blue book description is good at pointing out the relationship, it's somewhat inaccurate. As the ratio (zFar/zNear) increases, less precision is available near the back of the depth buffer and more precision is available close to the front of the depth buffer. So primitives are more likely to interact in Z if they are further from the viewer.

It's possible that you simply don't have enough precision in your depth buffer to render your scene. See the last question in this section for more info.

It's also possible that you are drawing coplanar primitives. Round-off errors or differences in rasterization typically create "Z fighting" for coplanar primitives. Here are some Drawing Lines over Polygons.

Why is my depth buffer precision so poor?

The depth buffer precision in eye coordinates is strongly affected by the ratio of zFar to zNear, the zFar clipping plane, and how far an object is from the zNear clipping plane.

You need to do whatever you can to push the zNear clipping plane out and pull the zFar plane in as much as possible.

To be more specific, consider the transformation of depth from eye coordinates

 xe, ye, ze, we

to window coordinates

 xw, yw, zw

with a perspective projection matrix specified by

 glFrustum(l, r, b, t, n, f);

and assume the default viewport transform. The clip coordinates of zc and wc are

 zc = -ze* (f+n)/(f-n) - we* 2*f*n/(f-n)
 wc = -ze

Why the negations? OpenGL wants to present to the programmer a right-handed coordinate system before projection and left-handed coordinate system after projection.

and the ndc coordinate:

 zndc = zc / wc = [ -ze * (f+n)/(f-n) - we * 2*f*n/(f-n) ] / -ze
     = (f+n)/(f-n) + (we / ze) * 2*f*n/(f-n)

The viewport transformation scales and offsets by the depth range (Assume it to be [0, 1]) and then scales by s = (2n-1) where n is the bit depth of the depth buffer:

 zw = s * [ (we / ze) * f*n/(f-n) + 0.5 * (f+n)/(f-n) + 0.5 ]

Let's rearrange this equation to express ze / we as a function of zw

 ze / we = f*n/(f-n) / ((zw / s) - 0.5 * (f+n)/(f-n) - 0.5)
         = f * n / ((zw / s) * (f-n) - 0.5 * (f+n) - 0.5 * (f-n))
         = f * n / ((zw / s) * (f-n) - f) [*]

Now let's look at two points, the zNear clipping plane and the zFar clipping plane:

 zw = 0 => ze / we = f * n / (-f) = -n
 zw = s => ze / we = f * n / ((f-n) - f) = -f

In a fixed-point depth buffer, zw is quantized to integers. The next representable z buffer depth away from the clip planes are 1 and s-1:

 zw = 1 => ze / we = f * n / ((1/s) * (f-n) - f)
 zw = s-1 => ze / we = f * n / (((s-1)/s) * (f-n) - f)

Now let's plug in some numbers, for example, n = 0.01, f = 1000 and s = 65535 (i.e., a 16-bit depth buffer)

 zw = 1 => ze / we = -0.01000015
 zw = s-1 => ze / we = -395.90054

Think about this last line. Everything at eye coordinate depths from -395.9 to -1000 has to map into either 65534 or 65535 in the z buffer. Almost two thirds of the distance between the zNear and zFar clipping planes will have one of two z-buffer values!

To further analyze the z-buffer resolution, let's take the derivative of [*] with respect to zw

 d (ze / we) / d zw = - f * n * (f-n) * (1/s) / ((zw / s) * (f-n) - f)2

Now evaluate it at zw = s

 d (ze / we) / d zw = - f * (f-n) * (1/s) / n
     = - f * (f/n-1) / s [**]

If you want your depth buffer to be useful near the zFar clipping plane, you need to keep this value to less than the size of your objects in eye space (for most practical uses, world space).