What a machine computes

The precision a depth buffer has left

Depth is stored as an affine function of one over the distance, so half of a buffer's codes are spent before the harmonic mean of the near and far planes — twenty centimetres out of a kilometre. The resolution goes as the square of the distance, and the fix that works is not more bits.

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 1/z1/z rather than in zz.

zndc=A+Bz,A=f+nfn,B=2fnfnz_{\text{ndc}} = A + \frac{B}{z}, \qquad A = \frac{f+n}{f-n}, \quad B = \frac{-2fn}{f-n}

That is the whole content of this essay. Everything below is a consequence of it, and every consequence is measured rather than described.

The code a depth buffer stores, and the code a linear map wouldDepth is stored as an affine function of 1/z, so the codes are spent near the eye: half of them are gone by 0.20 m. The straight line is a linear depth map over the same range, whose midpoint is the arithmetic mean at 500 m.00.2500.5000.7501-10123distance from the eye — log₁₀ metresfraction of the buffer's codes used uphalf the codes by 0.20 ma linear map, for comparisonnear 0.1 m, far 1000 mharmonic mean 0.20 m against arithmetic 500 m
Fig. 1 The buffer’s code against distance, with a linear map over the same range for comparison. The curve reaches half its range at 0.20 m and the straight line reaches half of its at 500 m. A 24-bit buffer with a near plane at 10 cm and a far plane at a kilometre has spent half its codes before the first traffic cone.

Where half the codes go, in closed form

The midpoint of the buffer is where zndc=0z_{\text{ndc}} = 0, so

A+Bz=0    z12=BA=2nfn+fA + \frac{B}{z} = 0 \implies z_{\frac12} = -\frac{B}{A} = \frac{2nf}{n+f}

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 1/z1/z is being halved, and halving a reciprocal is what a harmonic mean is.

For the usual pair of planes — n=0.1n = 0.1 m, f=1000f = 1000 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 2nf/(n+f)2nf/(n+f) 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 1/z1/z 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.

Where a depth buffer's codes actually goThe share of a 24-bit buffer used up within each distance, for a near plane at 0.1 m and a far plane at 1000 m. Ninety per cent of the range is spent inside the first metre; the last nine hundred metres share what is left.near 0.1 m · far 1000 m · 24-bit codeswithin 20 cm50.0%within 50 cm80.0%within 1 m90.0%within 5 m98.0%within 50 m99.8%half the codes are gone by 0.20 m — the harmonic mean of the two planesshare of the depth buffer used up within each distancearithmetic midpoint would be 500 m
Fig. 2 The same distribution as a set of bars. Ninety per cent of a 24-bit buffer is used up inside the first metre, and the far kilometre gets what is left. The share within a distance is (A + B/z + 1)/2, which is one line and is the same line the code curve comes from.

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:

dzndcdz=Bz2    Δz=2z2NB\frac{\mathrm{d}z_{\text{ndc}}}{\mathrm{d}z} = -\frac{B}{z^2} \implies \Delta z = \frac{2z^2}{N|B|}

with NN 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.

What a depth buffer can still resolve, against distanceΔz = 2z²/(N|B|) — quadratic in distance, so the resolution at 100 m is ten thousand times coarser than at 1 m. At the near plane it is 0.0 µm and at 100 m it is 6.0 mm.-8-6-4-20-10123distance from the eye — log₁₀ metresdepth separation the buffer can still resolve, log₁₀ metres (24-bit)1 m → 0.6 µm100 m → 6.0 mmnear 0.1 m, far 1000 m, 24-bit codesquadratic in distance
Fig. 3 The resolution curve on log axes, which is where a quadratic looks like a straight line of slope two. At a metre the buffer resolves six tenths of a micron, which is far finer than anything a scene contains; at a kilometre it resolves 60 cm, which is a person.

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 Δz\Delta z 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.

What one pixel of click error costs, against distanceA 1.83 m object at 3 m is measured to 0.28% per pixel; the same object at 201 m to 18.2% per pixel. The whole object shrinks toward the horizon, so a pixel buys more world.05101550100150200distance from the camera (metres)error in the recovered height, per pixel of click error (%)6 m — 0.55%25 m — 2.27%60 m — 5.43%120 m — 10.84%one pixel, on a 690 px picturelinear in distance
Fig. 4 The same structure from single-view metrology: an error in what is read maps to an error in what is recovered, by a factor that grows with distance. There the multiplier is the geometry of the measurement; here it is the quantisation of the store. In both cases the useful statement is the distance at which the answer stops meaning anything, rather than a percentage.

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 NN by 256, which divides Δz\Delta z 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: B=2nf/(fn)B = -2nf/(f-n), and for fnf \gg n that is very nearly 2n-2n, with the far plane almost absent. Halving the far plane from 1000 m to 500 m changes B|B| 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 B2n|B| \approx 2n, 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.

Clip space: the box the frustum becomes, and the plane the segment stops atA plan section through the frustum. The segment runs from 6.60 m in front of the eye to 1.19 m behind it, and the clipper stops it on the near plane at 0.40 m. Clipping happens here, before the divide, because both ends are still finite numbers at this stage.near 0.4 mtoward the far planethe eyein front1.2 m behind the eyeclipped hereeverything below the eye's line has a negative w, and dividing by it flips both signsplan section · near 0.4 m, far 26 mthe segment crosses the eye plane at t = 0.796
Fig. 5 The near plane doing its other job. Every metre it is pushed away from the eye is a metre of scene that gets cut, and every metre it is pulled toward the eye costs precision at every distance. The two are one setting with two independent pressures on it and no value satisfies both.

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.

Δz\Delta z 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 Δz\Delta z 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: Δz\Delta z is the separation above which the ordering is guaranteed, not the separation below which it is wrong. Two surfaces a tenth of Δz\Delta z 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.

How loose the test has to be before perspective preserves measureThe count of systems the table calls measure-preserving, against the tolerance. It sits at 6 across nine decades and then steps to 8 when the tolerance passes 15.6% — the drift a real pinhole picture actually produces. The exclusion in the table above is a statement about that boundary, and this is where the boundary is.02468-8-6-4-20log₁₀ of the tolerance on midpoint driftsystems counted as preserving measureperspective admitted at 15.6%the exclusion, sweptthe boundary is measured, not chosen
Fig. 6 The general shape of the distinction, from the 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 kNkk \mapsto N - k 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 1/z1/z 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 1/z1/z. 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.

The gap between walking the page and walking the surfaceFor a surface receding from 2 m to 8 m, the departure peaks at 0.3333 of the whole range — over half of it — at s = 0.6667. The closed form is (√k−1)/(√k+1) with k the depth ratio, and the marked point is where it says the peak is.-0.300-0.200-0.100000.2000.4000.6000.8001position across the drawn surfacehow far along the real surface, minus how far along the drawn one(√k−1)/(√k+1) = 0.3333at the page's midpoint, 30.0%depth ratio 4 : 1peak 0.3333 at s = 0.667
Fig. 7 The next consequence in the same chain, and one that shares the harmonic mean with this one. Interpolating across the page rather than across the surface makes an error whose peak is a closed form in the depth ratio alone — and at the page’s midpoint the error is exactly the drift that puts the page’s midpoint at the harmonic mean of the two depths.
The midpoint of one segment, under both familiesThe parallel projection places it exactly halfway (0e+0 px out). The perspective projection places it 22 px away from halfway, 7% of the drawn length.halfway along the drawn linethe actual midpointcorrect from 26 cm, at 160 mm wide22 px apart
Fig. 8 Where the harmonic mean came from originally on this site, four fields earlier. The drawn midpoint of a receding segment is not the midpoint of the real one, and the depth it does sit at is 2ab/(a+b). A depth buffer’s midpoint is that same quantity, arrived at from an entirely different direction.

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.

Where a depth buffer's codes actually goThe share of a 24-bit buffer used up within each distance, for a near plane at 0.6 m and a far plane at 1000 m. Ninety per cent of the range is spent inside the first metre; the last nine hundred metres share what is left.near 0.6 m · far 1000 m · 24-bit codeswithin 20 cm0.0%within 50 cm0.0%within 1 m40.0%within 5 m88.1%within 50 m98.9%half the codes are gone by 1.20 m — the harmonic mean of the two planesshare of the depth buffer used up within each distancearithmetic midpoint would be 500 m
Fig. 9 And the fix, drawn. With the near plane at 60 cm instead of 10, the share used up inside the first metre is 40.0% and half the codes are gone by 1.20 m rather than by 0.20. Nothing about the picture has changed — the same scene, the same camera, the same projection — and the buffer has become a different instrument.

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.

Named objects

A flat tag is an object no other essay names yet.

Camera matrixClip spaceConditioningDemonstrationDepth bufferHomogeneous coordinatesinstrument limitOcclusionQuantisationResidual