The precision a depth buffer has left
Worth reading first: The divide is postponed, not avoided · What happens behind the eye.
A depth buffer holds, for every pixel, a number saying how far away the nearest thing drawn there so far is. Every new surface compares its own number against the stored one and wins or loses. It is the simplest possible solution to the hidden-surface problem and it works, and everything interesting about it is in the word number.
The number is not the distance. It is an integer, in a fixed range, produced by a mapping that was chosen three steps earlier for a reason that had nothing to do with depth: the third row of the projection matrix has to survive the same divide the first two rows do, so what it produces is affine in rather than in .
That is the whole content of this essay. Everything below is a consequence of it, and every consequence is measured rather than described.
Where half the codes go, in closed form
The midpoint of the buffer is where , so
which is the harmonic mean of the near and far planes.
That is a satisfying result and it is worth pausing on, because the harmonic mean keeps appearing on this site and always for the same underlying reason. The page’s midpoint of a receding edge sits at the harmonic mean of its two depths. The perspective midpoint of a segment does the same. In each case something linear in is being halved, and halving a reciprocal is what a harmonic mean is.
For the usual pair of planes — m, m — it is 0.19998 m. The arithmetic mean, which is where a linear map would put its midpoint, is 500.05 m. Two and a half thousand times further.
The closed form is checked against a bisection on the code values themselves rather than quoted, because a formula agreeing with itself is not a measurement: the gate finds the midpoint by search and requires it to match to nine digits. And the control is the linear map, whose midpoint is asserted to be the arithmetic mean — which is what makes the number above a property of the storage rather than of the range.
The shape of the spending
Half the codes by 20 cm is the headline. The full distribution is more useful:
| within | share of the buffer |
|---|---|
| 20 cm | 50.0% |
| 50 cm | 80.0% |
| 1 m | 90.0% |
| 5 m | 98.0% |
| 50 m | 99.8% |
The last row is the one worth staring at. Everything from 50 m to the far plane at 1 km — the entire far half of most outdoor scenes — shares the last two tenths of one per cent of the buffer’s codes.
The resolution, and why it is quadratic
The practical question is not where the codes go but how far apart two surfaces must be before the buffer can tell them apart. Differentiate:
with the number of code steps. Quadratic in distance. For a 24-bit buffer with the planes above:
| distance | separation the buffer can resolve |
|---|---|
| 1 m | 0.60 µm |
| 10 m | 60 µm |
| 100 m | 5.96 mm |
| 1 km | 0.60 m |
A hundred times the distance is ten thousand times the step, and the gate asserts exactly that ratio rather than the two endpoints — because a closed form that is right at two places and wrong in between is a curve fit, and asserting the exponent is what says it is a law.
Z-fighting is not a bug in the comparison
The visible symptom of running out of depth resolution is two surfaces flickering against each other as the camera moves, pixel by pixel, in a pattern that changes every frame. It is usually described as a failure of the depth test.
It is not. The depth test is exactly correct — it compares two integers and returns the larger. What has happened is that the two surfaces have been given the same integer, because they are closer together than at their distance, and after that no comparison can separate them. Which one wins is then decided by whatever noise is left in the two computations, and the noise changes as the camera moves.
The distinction matters because it says where the fix has to go. Nothing done to the comparison helps; the information was destroyed at the quantisation step, which happened before the comparison ever ran. This is the same shape as the finding the metrology field records about reading a height from a photograph: past a certain distance the answer is not merely imprecise, the picture no longer contains the distinction being asked about.
The buffer is lopsided and it is not broken
It is easy to read all this as an indictment and it should not be. Two things are true at once and only the second is a problem.
Monotonicity survives. The code never decreases with distance — the gate checks 400 samples spread logarithmically across the whole range — so the depth test always gives the right answer when it gives an answer at all. A lopsided buffer is still a correct one. The only failure is the tie, and a tie is a genuine loss of information rather than a wrong comparison.
And the lopsidedness is in the useful direction, a little. Near things move faster across the frame, occlude more, and are looked at more closely, so spending precision near the eye is not simply a mistake. The trouble is the degree: nobody choosing a depth encoding would deliberately allocate ninety per cent of it to the first metre of a kilometre.
The fix that works, and the two that do not
More bits helps linearly and the problem is quadratic. Going from 24 bits to 32 multiplies by 256, which divides by 256 — a real gain, and the equivalent of moving 16 times closer. It buys a factor, not a change of shape.
Moving the far plane in barely helps at all. This is the counter-intuitive one and it falls straight out of the formula: , and for that is very nearly , with the far plane almost absent. Halving the far plane from 1000 m to 500 m changes from 0.20002 to 0.20004 — one part in ten thousand. The far plane is nearly irrelevant to depth precision, which is the opposite of the intuition that a smaller range should be more precise.
Moving the near plane out is the fix. Since , the resolution at any distance is inversely proportional to the near plane. Ten times the near plane is ten times finer everywhere. The gate checks this: at 100 m, moving the near plane from 0.1 m to 1 m improves the resolution from 5.96 mm to 0.60 mm.
That is a large, cheap, structural improvement, and its cost is that things closer than a metre get cut. Which is why the two settings are coupled and why “set the near plane as small as possible” is bad advice — the previous essay reaches the same conclusion from the clipping side, and the two pressures point in opposite directions.
What “resolution” means when the quantity is not a distance
There is a subtlety in the table above worth making explicit, because it is the kind of thing that makes a number look more meaningful than it is.
is the separation at which two surfaces get different codes. It is not an error bar on the stored depth, and treating it as one gets the direction of the guarantee backwards.
The buffer does not know how far away anything is; it knows an integer. Two surfaces separated by more than are reliably ordered. Two separated by less than it may be ordered correctly, may be ordered incorrectly, or may tie — and which of the three happens is decided by where the two surfaces fall relative to the quantisation boundaries, which is a property of their absolute positions rather than of their separation.
So the honest statement is: is the separation above which the ordering is guaranteed, not the separation below which it is wrong. Two surfaces a tenth of apart are ordered correctly nine times out of ten and wrongly the tenth, and the tenth is a pixel that flickers.
That is why z-fighting has the texture it does — speckled rather than solid, changing as the camera moves, worse where two surfaces are most nearly parallel. The failure is a boundary effect on a quantisation, and boundary effects are spatially incoherent by nature.
The same distinction runs through the site’s measurement fields. How wrong a measurement can be is careful to report an interval rather than a tolerance for exactly this reason: a quantity that maps a reading error into an answer error does not thereby give a bound on the answer, and the two get conflated constantly.
choices field. A test with a threshold reports a verdict, and the verdict’s meaning depends on how far from the threshold the case sits — which is not the same as the threshold being an error bar. A depth buffer’s Δz is a threshold on ordering, and it is regularly quoted as though it were a precision.Reverse-Z, and why it is free
There is a fourth option and it is nearly free, which makes it worth understanding rather than merely adopting.
Map the near plane to the top of the range and the far plane to the bottom — reverse the depth — and store the result in a floating-point buffer rather than an integer one. With integer codes this changes nothing whatever: it is a relabelling, and preserves every gap.
With floating point it changes everything, and the reason is a property of the representation rather than of the projection. Floating-point numbers are dense near zero and sparse away from it: between 0 and 1 there are about a billion representable single-precision values, and their spacing at 0.001 is a thousand times finer than at 1.0.
The unreversed mapping puts the far plane at 1.0, where float precision is worst, and the near plane at 0.0, where it is best — and the near plane is already where the mapping has spent all its codes. The two non-uniformities compound. Reversing puts the far plane at 0.0, where float precision is best, so the two non-uniformities very nearly cancel.
That is a satisfying result and it is worth being precise about what kind of result it is. It is not a better projection, and it does not change the geometry at all. It is an observation that two independent quantisations were stacked the wrong way round, and stacking them the other way is free.
What this field has now measured three times
Three essays, three departures, and the same shape underneath all of them.
The postponed divide is a decision made for clipping and interpolation, whose consequence is that the depth row must be affine in . The clipper is a decision forced by the divide, whose consequence is a near plane. The depth buffer’s precision is a consequence of both, and it is spent in a distribution nobody chose.
None of the three is a mistake. Each is the correct engineering answer to the problem in front of it, and the artefacts they produce are the compound interest on three good local decisions. That is a different thing from a bug, and it is the reason this field measures rather than complains.
A closing note on what is being stored
It is worth ending on the thing the whole essay assumes and never says.
A depth buffer stores a comparable quantity, not a distance. Nothing downstream ever needs to know how far away a surface is; it needs to know which of two surfaces is nearer. Every property in this essay follows from taking that seriously — the mapping only has to be monotone, so a monotone mapping that happens to fall out of a matrix row is good enough, and the price of “good enough” is a distribution of precision that nobody would design.
Which is the general form of what a machine adds to a projection, and it is what the two fields after this one are about. The geometry is exact. The arithmetic is finite. Every place the two meet, something is decided by what was convenient, and the consequence turns up somewhere the decision was not being made.
What links here
Computed from the collection, not written here: the essays that point at this one.
Shares its objects with
Essays that name at least two of the same things, and that neither author linked.
- A pixel is not a point — both name camera matrix, demonstration, quantisation, residual
- A frame is an interval — both name demonstration, instrument limit, residual
- The pixel that is not square — both name camera matrix, demonstration, residual
- The render is distorted on purpose — both name demonstration, instrument limit, residual
- A carpet and the people on it — both name demonstration, occlusion
- A turn of the head is not a step sideways — both name conditioning, residual
Named objects
A flat tag is an object no other essay names yet.
Camera matrixClip spaceConditioningDemonstrationDepth bufferHomogeneous coordinatesinstrument limitOcclusionQuantisationResidual