Learning to Love your Z-buffer.

We British pronouncethe letter 'Z' as "zed" - butAmericans pronounce it "zee".

...although that's just to makeThe Alphabet Song rhyme.

-- Steve

Introduction

One of the more common OpenGL programming problems that I
see concerns the poor precision of the Z buffer.

Many of the early 3D adaptors for the PC have a 16 bit Z
buffer, some others have 24 bits - and the very best have
32 bits. If you are lucky enough to have a 32 bit Z buffer,
then Z-precision may not seem to be an issue for you. However,
if you expect your program to be portable, you'd better give
it some thought.

The precision of Z matters because the Z buffer determines
which objects are hidden behind which others - and if you
don't have enough precision to resolve the distance between
two nearby objects, they will randomly show through each other
- sometimes in large zig-zags, sometimes in stripes.

This is commonly called 'flimmering' or 'Z-fighting' and it's
very disturbing to the user.

The Near Clip Plane

The near clip plane (zNear for short) is typically set using
gluPerspective() or glFrustum() - although it's also possible
to set it by setting the GL_PROJECTION matrix directly.

Some graphics programmers call zNear 'hither' and zFar 'yonder'.

Beginners frequently place zNear at a very short distance
because they don't want polygons close to the eye to be clipped
against the near plane - and because it isn't obvious why you'd
want to do anything else.

Positioning of zNear too close to the eye is the cause of
flimmering (in almost every case) - and the remainder of
this document explains why that is.

The Resolution of Z.

What people often fail to realise is that in nearly all machines,
the Z buffer is non-linear. The actual number stored in the
Z buffer memory is related to the Z coordinate of the object
in this manner:

This means that Z (and hence the precision of Z) is proportional to
the reciprocal of the z_buffer_value - and hence there
is a LOT of precision close to the eye and very little
precision off in the distance.

This reciprocal behaviour is somewhat useful because you
need objects that are close to the eye to be rendered in
great detail - and you need better Z precision for detailed
objects.

However, an undesirable consequence of this is that many of your Z
buffer's bits are wasted - storing insanely fine detail close to
the near clip plane. If you pull the near clip closer
to your eye, then ever more bits are dedicated to the
task of rendering things that are that close to you,
at considerable cost to the precision a bit further out.

It follows that in most cases, flimmering can be greatly
reduced - or even eliminated by moving the near clip plane
further from your eye.

Z Calculator

You can use this handy JavaScript calculator to see how your near and far
clip plane choices affect Z precision at various ranges. Make sure
you use the same units for all three distances - the precision result
is in those same units. Make a change to the inputs and hit return
and the results will be instantly recalculated.

Input Your Data Here:

Num.bits in Z buffer

(Usually 24 on modern graphics cards - 16 on older devices)

zNear

(This number should be as large as you can tolerate)

zFar

(This number can be very big for negligable penalty)

Z distance

(This should lie between zNear and zFar for meaningful results)

Results:

Resolution of Z Buffer at this range =

(The smallest depth separation it can resolve at this range)

Value in Z Buffer =

(The actual number that'll be written into memory)

How Bad Is It?

Talking about how large the error is for absolute values
of zNear (in feet, meters, lightyears or angstroms) is
meaningless. Given the math used to convert Z into the
number in the Z buffer, it's also fairly meaningless to
talk about absolute values in "OpenGL units" either.

Instead we have to think about the ratio of distances
to objects in your scene to the value of zNear.

This equation is approximate - it only applies if zNear is
much smaller than zFar - which is true for nearly all
applications.

For another way to think about this, suppose we choose to
think about the range at which there is a n% error in Z
due to the precision of the Z buffer. For ease of
discussion, I'll call the ratio of that range to the value
of zNear 'Zn%'.

Hence, Z5% is the "range at which there is a 5% error in
Z" divided by zNear.

For a 16 bit Z buffer, the value of Z5% is about 3500.
It varies *slightly* depending on the value of zFar,
and for very small values of zFar, it does get a little
bigger - but for practical applications, 3500 is a
good rule-of-thumb.

What this means in practice, is that if you place zNear
at 1 meter (in whatever units your database uses), then
when an object is at 3,500 meters, there will be a 5%
error in it's Z value.

* NB: The larger the range, the more the zFar distance starts
to affect the precision - but for most practical applications,
it doesn't make enough of a difference to matter.

The table tell us that value for Z1% is 666 and Z10% is ~8000.
So with our 1 meter zNear, we can expect better than 1%
precision below 666 meters, better than 5% precision below
3500 meters and better than 10% at under 8000 meters.

Now, if your zNear is at 10cm, you'll see a 5% error
at just 350 meters (3500*0.1m)- and out at 3.5km
meters, the error will be around 33% - over a kilometer
in error. An airplane flying behind a huge mountain
could suddenly pop into view in front of it when we are
are only a couple of miles away!

You can see that the placement of zNear is really
critical in a 16 bit Z system.

For a 24 bit Z buffer, the ratios are 256 times larger
so Z1% is ~170,000, and Z5% is about a million.

For a 32 bit Z buffer, even Z1% is about 45 million and
we are unlikely to care about Z5% and Z10% metrics!

Caveats

There are other sources of Z error (although with a
16 bit Z buffer, they are likely to be pretty negligable
compared to the error in storing Z).

Some machines offer alternative depth buffering algorithms.
These tend to fall into two catagories:

The W-buffer (aka OOZ-buffer - One-Over-Z) stores the
reciprocal of the usual Z-buffer value. This is generally
harder to compute and results in a W that is
essentially linear and has uniform error everywhere.
That drastically improves Z-precision at larger ranges,
but greatly reduces precision close to the eye where
it's needed. Several PC cards (eg 3Dfx) support OOZ
buffers, but not necessarily under OpenGL.

Floating point or Logrithmic Z (these are pretty similar
concepts actually). This also tends to even out the
distribution of bits over the Z range - but without
completely eliminating the improved resolution close
to the eye. SGI Infinite Reality machines do this - and
claim to get the equivelent *useful* Z precision using
15 bits as a conventional machine gets with 24bits.