What a machine computes

The divide is postponed, not avoided

A renderer does not divide by depth. It multiplies by a four-by-four matrix that carries the depth in a fourth coordinate and divides later, and the postponement is not an optimisation — it is what makes clipping and texture interpolation possible at all. The matrix and this site's pinhole put every point on the same pixel to five parts in a hundred trillion.

Worth reading first: Where parallel lines meet · Recovering the camera from the picture it drew.

Every projection on this site so far has been one line of arithmetic. A world point goes into the camera’s own coordinates, and then

u=cx+fxz,v=cy+fyzu = c_x + f\,\frac{x}{z}, \qquad v = c_y + f\,\frac{y}{z}

and that is the whole of it. Two divisions, and the picture exists.

A renderer does not do that. It multiplies the point by a four-by-four matrix, gets four numbers back, and divides by the fourth one somewhere further down the line — after a clipper has looked at all four, and in the case of a textured surface, not until the very last step of drawing each pixel. The divide is not skipped. It is postponed, and the interval between the multiply and the divide is where four of this field’s five essays live.

One projection, two routes: divide by depth, or multiply and divide laterThe same box through the site's pinhole and through a 4×4 projection matrix with the divide postponed until after clip space. The worst disagreement over all twelve edges is 4.0e-14 px, which is the noise floor of double precision rather than an approximation.x/z, y/z — the pinholeM·p, then divide by wworst disagreement 4.0e-14 px over 8 verticescorrect from 21 cm, at 160 mm wide42° across · near 0.1 m, far 1000 m
Fig. 1 The same box, drawn twice. On the left by the site’s own pinhole, dividing by depth on the spot. On the right by a 4×4 matrix whose result is divided only after clip space. The worst disagreement over all eight vertices is 5.7 × 10⁻¹⁴ px, which is the noise floor of double precision — the two routes are not similar, they are the same projection written twice.

Why this needs establishing rather than assuming

It would be easy to treat this as a formality. The projection matrix is in every graphics text; its derivation is standard; nobody doubts that it produces a perspective picture.

But this site’s whole method is that a figure is projected from a stated camera rather than constructed by a hand that knew what the answer should look like, and a second projector introduced without comparison is a second hand. If the matrix pipeline and the site’s own camera differ anywhere — a sign, a convention, a half-pixel — then every measurement in the three fields that follow is a measurement of a camera that appears nowhere else on the site, and the comparisons with the earlier fields would be comparisons of two different things.

So the matrix is built here in the site’s own camera coordinates, and the agreement is checked and printed rather than asserted in prose. The site’s gate for this field runs that comparison at seven fields of view from 18° to 130°, and the worst disagreement anywhere is 5.7 × 10⁻¹⁴ px.

The matrix, with nothing hidden in it

The usual presentation builds the projection matrix from a field of view, an aspect ratio, and a pair of clipping planes. That is a perfectly good recipe and it obscures the one thing worth seeing, which is that every entry is a focal length or a principal point. Written in the coordinates this site’s camera already uses — xx right, yy down the page, zz along the view — the matrix is

M=(2f/W02cx/W1002f/H12cy/H000AB0010)M = \begin{pmatrix} 2f/W & 0 & 2c_x/W - 1 & 0 \\ 0 & -2f/H & 1 - 2c_y/H & 0 \\ 0 & 0 & A & B \\ 0 & 0 & 1 & 0\end{pmatrix}

with AA and BB the two numbers that map the near and far planes onto the ends of the depth range. Multiply a world point written as (x,y,z,1)(x, y, z, 1) and four numbers come out, conventionally called xc,yc,zc,wcx_c, y_c, z_c, w_c — clip coordinates.

The fourth of them is zz. That is the entire trick: the bottom row of the matrix is (0,0,1,0)(0, 0, 1, 0), so ww is the depth, carried forward untouched. Everything a graphics text says about homogeneous coordinates and the projective plane is true and is not needed to see what is happening here. The matrix has arranged for the depth to survive the multiplication, so that the division by it can happen when somebody is ready.

The first two rows are the pinhole with the divide taken out and a rescaling to the range 11-1 \ldots 1 applied. The second row is negated because normalised device coordinates put yy upward and this site’s images put it down the page — two conventions, one sign, and no content.

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 2.99 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 front3.0 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.647
Fig. 2 The frustum in plan, with the near plane marked. Clip space is the box this shape becomes: the matrix maps the pyramid onto a cube, which is why “is this point visible” is four comparisons rather than a ray test. The segment shown runs from in front of the eye to behind it, and the clipper stops it on the near plane.

The first reason to wait: linearity

Clip coordinates are linear in the world point, and the pixel coordinates are not.

That distinction is the whole justification for the postponement, and it is checkable rather than rhetorical. Take two world points, take their midpoint, and run all three through the matrix: the midpoint’s clip coordinates are the average of the other two’s, to 2×10162 \times 10^{-16}. Then take the same three points’ pixels: the midpoint’s pixel is 22.1 px away from the average of the other two.

The site has been saying this since its foundation, from the other side. A projection destroys the ratio in which a point divides a segment — that is the reason a perspective picture cannot be measured with a ruler, and the reason the cross-ratio is worth as much as it is. What the matrix does is delay the destruction. Before the divide, everything is still affine and midpoints are still midpoints; after it, they are not.

Four points on a line, before and after a projectionLength and the ratio of lengths do not survive the projection; the cross-ratio does, agreeing to 0e+0 relative.horizonABCDon the groundin the picturelength AB1.00011.3930ratio AB:CD0.56670.6837cross-ratio1.31681.3168correct from 26 cm, at 160 mm wide34° across
Fig. 3 The quantity that survives a projection, which is the reason the loss of the midpoint matters. Four points on a line, projected: their spacings all change and their cross-ratio does not. Clip space keeps the spacings too — it is the divide, and only the divide, that gives them up.

Anything a renderer wants to do by interpolation — clipping a segment at a plane, walking a triangle’s edge, blending a vertex attribute — is easier on the side of the divide where interpolation is honest. That is not a small convenience. It is the difference between a linear equation and a rational one at every step of the pipeline.

The second reason to wait: points behind the eye

This site’s camera refuses to project a point at or behind the eye plane, and the reason is written beside it: returning nothing is “a refusal rather than a wrapped-around point appearing somewhere plausible in the picture.”

That refusal is not fussiness. Divide xcx_c and ycy_c by a negative ww and both signs flip, so a point behind the eye lands through the principal point on the far side of the frame, at a perfectly ordinary-looking position with nothing whatever to mark it as impossible. A segment with one end in front and one behind is then drawn as a line running the wrong way across the whole picture — straight, inside the frame, and reversed.

A clipper is what makes that impossible, and it can only work before the divide, because before the divide both ends of the segment are still finite numbers with a sign that says which side of the eye they are on. After the divide, the far endpoint has already become nonsense and there is nothing left to interpolate along.

The same grid, clipped and not: straight lines running the wrong wayA ground grid extending 2.4 m behind the eye. Clipped, the lines stop at the near plane. Unclipped, the parts behind the eye are divided by a negative w, which flips both signs and lands them through the principal point on the far side — straight, inside the frame, and reversed. The direction cosine of the two drawings of one segment is -1.0000.clipped at the near planedivided without clippingdirection cosine -1.0000 — the far end is drawn 2142 px away20 grid segments, 2.4 m of them behind the eyea reversed line is not a large error, it is a different picture
Fig. 4 The same ground grid, clipped and not. The right-hand panel is not a mess — that is the point. Its lines are straight, they are inside the frame, and the direction cosine between one segment’s two drawings is −1.0000: they run in exactly opposite directions.

The next essay is about that picture and nothing else. What matters here is that the reason for the postponement and the reason for the clipper are the same reason, stated twice: the fourth coordinate is the only thing that knows which side of the eye a point is on, and dividing by it throws that knowledge away.

The third reason to wait: what a surface carries

The third reason is the one this field’s last essay is about, and it is worth naming now because it explains why ww is carried past the clipper and all the way into the drawing of individual pixels.

A surface being drawn carries quantities that vary across it — where in a texture each point sits, which way the surface faces, what colour it was assigned at each corner. Interpolating those linearly across the page is wrong, and the size of the error is a closed form: for a surface receding from depth aa to depth bb, the worst departure is

k1k+1,k=b/a\frac{\sqrt{k}-1}{\sqrt{k}+1}, \qquad k = b/a

which at a depth ratio of ten is 0.52 — more than half the whole range of whatever is being interpolated.

The fix is to interpolate u/wu/w and 1/w1/w across the page, both of which really are linear there, and divide at the very end. So ww has to still exist at the point where individual pixels are being written, which is about as late as a postponement can be taken.

The gap between walking the page and walking the surfaceFor a surface receding from 2 m to 20 m, the departure peaks at 0.5195 of the whole range — over half of it — at s = 0.7597. 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.400-0.200000.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.5195at the page's midpoint, 40.9%depth ratio 10 : 1peak 0.5195 at s = 0.760
Fig. 5 Why the fourth coordinate survives all the way to the last step. The curve is the gap between how far along the real surface you are and how far along the drawn one, for a surface receding 2 m to 20 m. Its peak is (√k−1)/(√k+1) and the marked point is where the closed form says the peak is.

The depth row, which is the only part with content of its own

The first two rows of the matrix are the pinhole rearranged. The third row is not — it is a decision, and a consequential one.

It maps camera depth onto a value that will be stored and compared, and the mapping chosen is

zndc=A+Bzz_{\text{ndc}} = A + \frac{B}{z}

with A=(f+n)/(fn)A = (f + n)/(f - n) and B=2fn/(fn)B = -2fn/(f-n) for near plane nn and far plane ff. That is affine in 1/z1/z, not in zz, and that single choice is why a depth buffer spends ninety per cent of its precision inside the first metre.

The choice is not arbitrary, and it follows from the postponement itself. Whatever goes in the third row has to survive the same divide by w=zw = z that the first two rows do, and a value that is affine in 1/z1/z before the divide is exactly a value that is affine in zz after it — which is what a matrix can produce. A depth that was linear in zz after the divide would need a matrix entry that was quadratic in zz before it, and a matrix does not have one.

So the depth buffer’s famous lopsidedness is a consequence of a decision made three steps earlier for a completely different reason. That is worth stating plainly because it is the shape of most of the findings in this field: the machine’s departures from the ideal projection are not carelessness, they are the price of doing the projection in a particular order.

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. 6 What the third row costs. The curve is the fraction of the depth buffer used up by each distance, and the straight line is what a linear map over the same range would give. Half the codes are gone by 0.20 m, which is the harmonic mean of the near and far planes; the arithmetic mean is 500 m.

The near and far planes are not a rendering detail

A pinhole camera has no near plane and no far plane. Every point in front of the eye has an image, and points at any distance have perfectly good ones — the whole metrology field depends on that, because a measurement made from a picture cannot have a nearest measurable object.

The matrix has two, and they are the price of putting the depth in a matrix row at all. Fixing AA and BB requires naming two distances, and every consequence in this field’s third essay follows from which two.

The near plane also has a geometric meaning worth stating rather than treating as a parameter. It is where the clipper cuts, so it is the closest anything can be and still be drawn — and setting it too small is the commonest way to ruin a depth buffer. Setting it too large cuts holes in things the camera is nearly touching. Neither has an obviously right answer and the trade is quantified in the essay on what precision is left.

What the matrix is not

Two things this presentation deliberately avoids, because both are the sort of claim that sounds deep and is not.

It is not “perspective is a linear transformation”. Perspective is not linear and the matrix does not make it so; the matrix is a linear step followed by a nonlinear one, and the nonlinearity is exactly where it always was.

And it is not a statement about projective geometry that this site needs. Homogeneous coordinates are a genuinely beautiful idea and their power here is entirely practical: they let one matrix represent a projection, they make a point at infinity a value rather than a special case, and they let the divide be deferred. The site already uses the second of those constantly — a vanishing point is the image of a point at infinity, and this site’s projective machinery has represented lines as three numbers since the first commit for exactly that reason.

A family of parallel ground lines at 30°, and where they meetAll five lines pass through one point on the horizon, off the edge of the frame at x = 1464. The point fitted from the drawn lines agrees with the one computed from the direction to 1e-11 px, and the fit's own residual is 2e-12 px.horizon — the image of the line at infinityvanishing point at x = 1464 — off the framecorrect from 26 cm, at 160 mm wide34° across
Fig. 7 The point at infinity that this site has been drawing since its foundation. A direction has a vanishing point, and in homogeneous coordinates it is an ordinary value with a zero in the last slot rather than a case the code has to test for. That is the same convenience the projection matrix exploits.

The check that matters, and the one that would not

Comparing the matrix pipeline with the pinhole is worth something precisely because the two are written independently — one divides on the spot and one carries four coordinates through a clipper — and it would be worth nothing if the pipeline had been written by expanding the pinhole’s formula.

There is a second check with the same shape and more content, and it is the site’s own. Draw the box with the matrix, throw the camera away, and recover it from the drawn edges: three bundles of parallel edges give three vanishing points, and three mutually orthogonal directions determine the focal length and the principal point. The recovery is shown the picture and never the camera.

A box drawn from a known camera, and the camera recovered from the drawingThree vanishing points found from the twelve drawn edges alone give back the focal length to 4e-15 relative.recovered principal pointused to drawrecoveredgapfocal length853.90853.904e-15principal x345.0345.02e-12angle44.0°44.0°correct from 20 cm, at 160 mm wide44° across
Fig. 8 The site’s round trip, which the matrix pipeline has to survive as the pinhole does. The three vanishing points come from the drawn edges alone, and the focal length and principal point come from the triangle they make. A pipeline with a sign error somewhere would still draw a picture; it would not draw one whose recovered camera is the one that drew it.

It does survive, at the same precision the pinhole does. That is the licence the rest of this field runs on: every departure measured in the next four essays is a departure from this camera, which is the one the whole site is built on.

What the postponement actually buys

Set the three reasons side by side and one thing is common to all of them.

Clipping needs the depth’s sign, which the divide destroys. Interpolation across an edge needs linearity, which the divide destroys. Interpolation across a surface needs the depth itself, which the divide consumes.

Each of the three wants the same thing — the depth, still present, still separate — and the matrix’s fourth coordinate is what supplies it to all three from one multiplication. That is a genuinely good piece of engineering and it is worth admiring for the right reason: not because a 4×4 matrix is elegant, but because three unrelated problems turn out to have one prerequisite and it can be met once.

The cost is that the depth then has to be stored in some finite number of bits, mapped by a row of a matrix that could not be anything but affine in 1/z1/z, and compared against other depths stored the same way. Which is the next-but-one essay, and is where this field stops being an appreciation and starts being a measurement.

Where the sample sits inside the pixel, and what a recovery calls the differenceSampling at the pixel's corner instead of its centre translates every mark by half a pixel in each axis — 0.7071 px — and the site's own recovery reads that as a principal point 0.707 px from where it should be, with the focal length changed by 4.5e-13 px. The other half-pixel error, mapping the viewport onto W−1 pixels instead of W, does the opposite: it leaves the principal point alone and shortens the focal length by 1.30 px.one pixel is an areacentrescornersprincipal point moves0.707 pxfocal length changes by4.5e-13 pxan edge-versus-centre viewport1.303 pxa half-pixel convention is a principal-point error; an off-by-one viewport is a focal-length error8 vertices, all shifted by the same 0.7071 pxspread across marks 0.0e+0 px
Fig. 9 And the last step of the pipeline, which the postponement has nothing to do with and which costs the same order of magnitude. Where the sample sits inside a pixel is a convention; getting it wrong translates every mark by half a pixel, and this site’s own recovery reads that as a principal point in the wrong place with the focal length untouched.
One projection, two routes: divide by depth, or multiply and divide laterThe same box through the site's pinhole and through a 4×4 projection matrix with the divide postponed until after clip space. The worst disagreement over all twelve edges is 2.8e-14 px, which is the noise floor of double precision rather than an approximation.x/z, y/z — the pinholeM·p, then divide by wworst disagreement 2.8e-14 px over 8 verticescorrect from 10 cm, at 160 mm wide76° across · near 0.1 m, far 1000 m
Fig. 10 The agreement again, at a much wider field of view, because “they agree” is a claim about a function and not about a setting. Seven fields of view from 18° to 130° are checked in the gate and the worst disagreement anywhere is 5.7 × 10⁻¹⁴ px.

What links here

Computed from the collection, not written here: the essays that point at this one.

Reads more easily once this is understood

Essays that name this one as worth reading first.

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 matrixcentre of projectionClip spaceDemonstrationFocal lengthHomogeneous coordinatesPinholepoint at infinityPrincipal pointProjective map