What a machine computes

A point at infinity is an ordinary vertex

Give a vertex a zero in its fourth slot and it stops being a point and becomes a direction — and the projection matrix draws it anyway, through the same multiply and the same divide, on that direction's vanishing point to seven trillionths of a pixel. Slide the eye ten metres and it does not move. Two of them bound a ground that reaches the horizon, where a ground drawn to ten kilometres stops a fifth of a pixel short.

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

The divide is postponed wrote the projection matrix out with nothing hidden in it and showed that its fourth output is the depth, carried forward so that the division by it can wait. It ended one figure with a claim about the fourth input: that a direction, written in homogeneous coordinates, is an ordinary value with a zero in the last slot rather than a special case a renderer has to check for.

That claim was made in a caption. It is worth measuring, because it says something stronger than that directions can be written down. It says a renderer can hand the matrix a vertex that is not a point at all — a point at infinity — and the matrix, the clipper and the divide will all treat it exactly as they treat a corner of a box, and put it in the right place.

A vertex with a zero in its last slot is drawn at its vanishing pointA box drawn through a 4×4 projection matrix, and 6 vertices more with w = 0: the directions of the box's three families of edges and three directions along the ground, each signed to point in front of the eye. Every one goes through the same matrix and the same divide as a corner of the box and lands on the vanishing point the pinhole gives its direction, to 7e-12 px. One of them falls inside the frame, and the three ground directions' vertices lie on one line, the horizon, to 1e-13 px.6 vertices with w = 0 · each on its vanishing point to 7e-12 pxcorrect from 17 cm, at 160 mm wideedges extended to where their w = 0 vertex lands
Fig. 1 A box drawn through a 4×4 projection matrix and six more vertices with w = 0 — the directions of its three families of edges and three along the ground. Each lands on its direction’s vanishing point to 7e-12 px; the three ground directions’ vertices lie on the horizon to 1e-13 px.

The same multiply, the same divide

A world point enters the matrix as (x,y,z,1)(x, y, z, 1). The last coordinate multiplies the camera’s position: moving from world coordinates into the camera’s is a rotation of the point’s coordinates and a subtraction of the eye, and the subtraction is scaled by that 1. Write a 0 there instead and the subtraction disappears, leaving only the rotation. What goes into the matrix is then the direction as the camera sees it, and what comes out has ww equal to how fast that direction gains depth.

The divide by that ww is the step where parallel lines meet describes from the other side. Every line with that direction runs toward one point in the picture, and a point far along any of them is drawn ever closer to it; dividing the direction itself by its own depth rate is the limit taken in one step. Nothing in the pipeline has to know that it is being asked for a limit.

The first figure asks for six of them. Three are the directions of the box’s families of edges, three more run along the ground, each signed so that it points ahead of the eye. All six go through the matrix and the divide unmodified and land on the vanishing points the pinhole computes for the same directions, the worst of them 7×10127 \times 10^{-12} px away. Only one of the six happens to fall inside the frame — vanishing points of a box seen at an angle usually lie far outside it — and the construction lines carry each family’s edges out to where its vertex lands. The three ground directions’ vertices lie on a single line to 101310^{-13} px, which is the horizon: the image of the ground’s line at infinity is the line through the images of its points at infinity.

The last coordinate is one over a distance

A zero is the end of a scale, and the rest of the scale is worth drawing.

The last coordinate is a dial from here to the horizonA vertex (d + w·P, w), with d a direction along the ground and P a point near the box: the point 1/w metres from P along d. Dialled from w = 1 to w = 0.001 — from a metre away to a kilometre — its image is 397 px, 351 px, 261 px, 183 px, 114 px, 54 px, 29 px, 15 px, 6.02 px, 3.03 px from the direction's vanishing point, and at w = 0 it is on it, 1e-12 px. The distance is one constant times w divided by the direction's depth rate plus w times the depth of P — a depth of 5.93 m and a rate of 0.887 m of depth per metre along d — to a part in a billion; so it approaches proportion to w as 1/w outgrows 6.7 m — a slope of 0.975 on logarithmic axes between w = 0.01 and 0.001. Every setting agrees with the pinhole's picture of the same point to 7e-13 px.0.0010.010.1131030100300the vertex's last coordinate, w (one over metres along the ground, log scale)image distance from the direction's vanishing point (px, log scale)w = 0: 1e-12 px from the vanishing pointslope 0.975 as w → 0
Fig. 2 The vertex (d + w·P, w), the point 1/w metres from P along the ground, dialled from w = 1 to w = 0.001: its image is 397 px from the direction’s vanishing point at w = 1, 29 px at 0.01, 3.03 px at 0.001, and 1e-12 px at w = 0.

The vertex (d+wP, w)(\mathbf{d} + w\,\mathbf{P},\ w) is, after dividing through by ww, the point P+d/w\mathbf{P} + \mathbf{d}/w: a point near the box, pushed 1/w1/w metres along a ground direction. At w=1w = 1 it is a metre out and its image is 397 px from the direction’s vanishing point. At w=0.1w = 0.1, ten metres, 183 px. At w=0.01w = 0.01, a hundred metres, 29 px; at w=0.001w = 0.001, a kilometre, 3.03 px. At w=0w = 0 it is on the vanishing point. Every setting agrees with the pinhole’s own picture of the same point to 7×10137 \times 10^{-13} px, because it is the same point.

The dial is not quite linear, and the exact form says why. The image’s distance from the vanishing point is

wdz+wz0\frac{w}{d_z + w\,z_0}

times a constant, where z0z_0 is the depth of the starting point — 5.93 m — and dzd_z is how much depth the direction gains per metre, 0.887. The constant holds across the whole dial to a part in a billion. So the distance becomes proportional to ww only once 1/w1/w is long against z0/dzz_0/d_z, 6.7 m; between a hundred metres and a kilometre the slope on logarithmic axes is 0.975 rather than 1, because the starting point’s own depth still counts for a few per cent at a kilometre. The last coordinate is one over a distance, and the image’s offset from the vanishing point is one over a depth — the same currency depth is a reciprocal found a disparity is measured in. A vertex at w=0w = 0 is at the far end of that reciprocal scale, where it reads zero.

Why the eye’s position drops out

The zero multiplies the eye’s position, so a vertex with that zero cannot see where the eye is.

Slide the eye and a vertex at infinity does not moveThe camera slid sideways by 0.1, 1, 10 m without turning. The box's corners move by up to 15 px, 140 px, 900 px; points 100 m away along the ground by 2.27 px, 23 px, 213 px; vertices with w = 0 by 3e-11 px, 3e-12 px, 3e-11 px — nothing at any slide, because the eye's position enters a vertex's camera coordinates multiplied by its w.0.1 m slide · corners of the box15 px0.1 m slide · points 100 m along the ground2.27 px0.1 m slide · vertices with w = 03e-11 px1 m slide · corners of the box140 px1 m slide · points 100 m along the ground23 px1 m slide · vertices with w = 03e-12 px10 m slide · corners of the box900 px10 m slide · points 100 m along the ground213 px10 m slide · vertices with w = 03e-11 pxw = 0: 3e-11 px at every slidethe camera turns not at all
Fig. 3 The camera slid sideways by 0.1, 1 and 10 m without turning. The box’s corners move by up to 15, 140 and 900 px, points 100 m away by 2.27, 23 and 213 px, and vertices with w = 0 by 3e-11, 3e-12 and 3e-11 px.

Slide the camera sideways without turning it. A tenth of a metre moves the box’s corners by up to 15 px, and a point a hundred metres away along the ground by 2.27 px. A metre moves them 140 and 23 px; ten metres, 900 and 213 px. The vertices with w=0w = 0 move by 3×10113 \times 10^{-11} px at the smallest slide and at the largest — nothing, in both cases, to the rounding of the arithmetic.

That is parallax run to its limit. A point moves across the picture, as the eye slides, by the slide over its distance; at w=0.01w = 0.01 that is a hundredth of what a point a metre away does, and at w=0w = 0 it is none. It is also the reason a renderer can draw a sky, the stars or the direction of a distant light as vertices with w=0w = 0 and move the eye freely beneath them: nothing needs to be moved with the camera to keep them fixed, because the matrix never adds the camera’s position to them in the first place. Turning the camera does move them, exactly as it moves every vanishing point, because the rotation is the part of the transformation the zero does not remove.

Two vertices at infinity make a horizon

A triangle is three vertices, and nothing requires all three to be points.

A ground with its far corners at infinity reaches the horizon; a finite one stops shortA ground triangle from a point near the box to two directions along the ground, through a matrix with no far plane. Given its two far vertices with w = 0, its far edge lies on the horizon: the region reaches it exactly. The same triangle with its far vertices 100, 1000, 10000 m out stops 18.59, 2.04, 0.21 px below the horizon at the middle of its far edge. Each far corner's distance below the horizon times its depth is the same 1211.5 px·m at every reach, so the gap falls as one over the depth and is zero at no finite reach.far edge 100 m out: 18.59 px short · w = 0: on the horizoncorrect from 17 cm, at 160 mm wideno far plane
Fig. 4 A ground triangle with its two far vertices at w = 0, through a matrix with no far plane, reaches the horizon. With its far vertices 100, 1000 and 10000 m out it stops 18.59, 2.04 and 0.21 px short.

Take a ground triangle from a point near the box to two directions along the ground, and give its two far vertices w=0w = 0. Its far edge is then the segment between two vanishing points of the ground, which lies along the horizon, and the drawn region reaches the horizon exactly. The picture of an infinite ground plane is a finite polygon bounded by a line in the picture, and the matrix produces it from three ordinary vertices.

A finite triangle cannot do this at any size. With its far vertices a hundred metres out, the middle of its far edge stops 18.59 px below the horizon; a kilometre out, 2.04 px; ten kilometres, 0.21 px. Each far corner’s distance below the horizon, multiplied by its depth, comes to the same 1211.5 px·m at every reach, because a point on the ground sits below the horizon by the eye’s height over its depth, scaled by the focal length — the horizon being the image of the ground’s line at eye level — so the gap falls as one over the depth and is zero at no finite depth. A ground drawn to a very long way is very nearly at the horizon; a ground drawn to infinity is at it.

The far plane cannot hold a point at infinity

That triangle had to be drawn through a matrix with no far plane, and the reason is the depth row.

Every far plane clips the point at infinity; a matrix without one keeps itA vertex with w = 0 along the ground, through matrices with a near plane at 0.1 m and far planes at 100, 1000, 10000 m. After the divide its depth is 1.002002, 1.000200, 1.000020 — beyond the far plane's 1 by 2n/(f − n) each time, so every finite far plane clips it, however far away it is put. With no far plane its depth is exactly 1, and it is kept. The opposite direction, which the pinhole draws at the same vanishing point, comes out with w = −0.887 and is clipped as behind the eye.matrixdepth after the divideclip testfar plane at 100 m1.002002clippedfar plane at 1000 m1.000200clippedfar plane at 10000 m1.000020clippedno far plane1.000000keptno far plane, the opposite directionw = −0.887clippedbeyond the far plane by 2n/(f − n): 2002, 200, 20 millionthsnear plane 0.1 m
Fig. 5 A vertex with w = 0 through matrices with a 0.1 m near plane and far planes at 100, 1000 and 10000 m: its depth after the divide is 1.002002, 1.000200 and 1.000020, and it is clipped. With no far plane it is exactly 1 and kept; the opposite direction has w = −0.887 and is clipped.

The depth row maps a camera depth zz to A+B/zA + B/z after the divide, with A=(f+n)/(fn)A = (f + n)/(f - n) for near plane nn and far plane ff. A vertex with w=0w = 0 has no finite depth, and its depth after the divide is AA itself — the value the row approaches as zz grows without limit. For every finite far plane, AA is greater than one by 2n/(fn)2n/(f - n): 1.002002 with the far plane at a hundred metres, 1.000200 at a thousand, 1.000020 at ten thousand. The clipper keeps depths up to 1. So every finite far plane clips every point at infinity, by a margin that shrinks as the far plane recedes and never reaches zero; putting the far plane further away never lets the sky through.

The matrix with no far plane has A=1A = 1 exactly, and there a vertex with w=0w = 0 comes out at a depth of exactly 1 and is kept. One plane is nearly free priced that matrix against a merely distant far plane in the currency of the depth buffer and found the difference negligible. This is the other half of the case for it: it is the only projection matrix that can draw the vertices this essay is about.

The sky is the last code in the buffer

A vertex kept at a depth of exactly 1 is stored at the far end of the depth buffer, and that turns out to be the right place for it without any further arrangement.

With the depth stored as a 24-bit code — the buffer whose lopsided precision the precision a depth buffer has left measured — the far end is code 16,777,215, the last one there is. A vertex with w=0w = 0 lands on it exactly. Every finite point lands below it: with the near plane at 0.1 m, a point 10 m away is 167,772 codes below the sky, a point a kilometre away 1,678 codes, ten kilometres 168, a hundred kilometres 17, a thousand kilometres 2. A depth test that keeps the nearer of two things therefore draws every piece of terrain in front of the sky, and the sky behind all of it, in whatever order they arrive. There is no need to draw the sky first, or last, or with depth testing switched off; its vertices put it behind everything by being at infinity.

The arrangement does fail, and where it fails can be written down. A finite point sits Nn/zNn/z codes below the sky, for NN codes and near plane nn, and it rounds onto the sky’s own code once that is under a half — beyond z=2Nnz = 2Nn, which for a 0.1 m near plane is 3,355 km. A point 3,000 km away is still one code in front of the sky; one at 3,400 km has the sky’s code and ties with it. Pulling the near plane in to a centimetre brings the tie in to 336 km, and pushing it out to a metre sends it to 33,554 km. The near plane decides how far away the ground can be before it is indistinguishable from infinity, which is the same plane one plane is nearly free found deciding everything else about the buffer.

A direction and its opposite are different vertices

The last row of that figure is the least obvious.

To the pinhole, a direction and its opposite have the same vanishing point: the line through the eye in either direction meets the picture plane in one place, and the pinhole’s formula flips the sign so that both come out there. The matrix does not flip anything. A direction pointing ahead of the eye has ww greater than zero; its opposite, pointing behind, has the same magnitude and ww below zero — −0.887 for the ground direction in the figure — and the clipper rejects it, because a vertex with negative ww is behind the eye.

So in homogeneous coordinates as a renderer uses them, (d,0)(\mathbf{d}, 0) and (d,0)(-\mathbf{d}, 0) are not the same point, though they project to the same place. The sign of the fourth output records which side of the eye the direction points to, which is the fact what happens behind the eye turned on for ordinary points: the divide is wrong for w<0w < 0, and the clipper is what keeps it from being asked. A sky drawn with vertices at infinity is therefore drawn only in front of the camera, which is where it can be seen, and the half of it behind the camera is thrown away for exactly the reason a point behind the camera is.

A recovery reads these vertices backwards

The matrix takes a direction with w=0w = 0 to a vanishing point. Recovering the camera runs the other way: from the vanishing points of perpendicular directions in a photograph, it reads back the focal length and the principal point with no knowledge of the lens. The two are the same map in opposite directions, and the round trip says exactly what the forward map keeps.

Given the camera, a vanishing point (u,v)(u, v) gives back the direction ((ucx)/f, (vcy)/f, 1)((u - c_x)/f,\ (v - c_y)/f,\ 1) in the camera’s coordinates — the vertex that produced it, up to its length. Length is the first thing lost, and it was never information: a direction and twice that direction are the same direction, and the divide by ww removed the scale on the way in.

Sign is the second thing lost, and it was information. The pixel is the same for (d,0)(\mathbf{d}, 0) and (d,0)(-\mathbf{d}, 0), so a recovery handed only a vanishing point cannot say whether the edges that meet there recede ahead of the camera or come toward it from behind. The matrix knew — it carried the sign in ww until the divide — and the picture does not. A recovery resolves it by assuming the scene is in front of the camera, which for a photograph it is; the clipper resolves it by throwing the other half away. Both are the same decision about which half of the directions a picture can be made of.

An eye at infinity is the same zero

The zero has one more appearance, in the matrix rather than the vertex.

The divide is postponed found that the matrix’s bottom row decides the drawing system: (0,0,1,0)(0, 0, 1, 0) divides by depth and is a perspective, while (0,0,0,1)(0, 0, 0, 1) divides by nothing and is a parallel projection. The second is the perspective whose centre of projection has itself been given a zero — an eye at infinity, whose rays all share one direction, the limit the eye taken to infinity is named for. So the same zero, placed in a vertex, makes a point into a direction and draws it at a vanishing point; placed in the eye, makes the centre into a direction and draws everything without one.

That symmetry is the practical content of homogeneous coordinates in a renderer. There is no separate code for vanishing points, for skies, for grounds that reach the horizon, or for orthographic views. There is one matrix, one clipper and one divide, and the special cases are ordinary values with a zero in the right place.

What this does not settle

Interpolation. Every figure here places vertices. Stepping an attribute across a triangle whose vertex has w=0w = 0 uses one over ww at that vertex, which is not a number; how an attribute such as a texture coordinate should behave as the drawn region approaches the horizon was not measured.

Rasterising the whole triangle. The ground triangle’s vertices were all in front of the eye. A triangle with some vertices behind the eye and one at infinity has to be clipped before it is drawn, and how the clip behaves at w=0w = 0 exactly was not drawn.

Precision. The vertices here are exact directions in double precision. A direction stored in the floating-point format a graphics processor uses, or a point with ww small but not zero, loses digits in the divide, and how many was not counted.

Still open: a texture coordinate that runs to the horizon

A ground drawn to infinity carries a texture, and the texture coordinate at its far vertices is the coordinate of a point at infinity — which, for a repeating ground texture, is not a number either. The correct interpolation of a texture that does not interpolate on the page interpolates the attribute over ww and one over ww and divides at the end, and at a vertex with w=0w = 0 both of those are infinite.

The question that leaves is whether the interpolation can be written without them: whether an attribute given at a vertex as a rate — so much texture per metre along the direction — rather than a value makes the ground triangle’s texture exact everywhere up to the horizon, and how the texture’s density on the page behaves as it approaches that line.

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 spaceHomogeneous coordinatesHorizonpoint at infinityProjective mapVanishing point