What a machine computes

A sky is carried as a direction

A triangle of sky has all three vertices at infinity, so the weight that lets a texture reach the horizon is zero everywhere and there is nothing to divide by. The attribute that belongs to such a triangle is the direction itself: carried over w like any other, it is every pixel's own ray to 5e-14 degrees. Carry the vertices' azimuth and elevation instead and a 60° triangle is 7.7° out, a 4096-texel sky needs triangles under ten degrees wide, and a triangle across the seam where azimuth wraps is painted with the opposite sky.

Worth reading first: The divide is postponed, not avoided · A texture does not interpolate on the page.

A texture reaches the horizon as a rate solved the problem of a ground drawn to infinity. A repeating texture has no coordinate at a vertex that is a direction, but it has a rate — so many checks per metre along the direction — and a second number, a weight, that is one at points and zero at directions. Carried over the clip ww like every attribute and divided once at the end, that pair gave the ground’s own texture coordinate to three parts in 101510^{15} half a pixel from the horizon.

The solution needed one vertex that was a point. The weight is interpolated across the triangle, and the division is by the interpolated weight; a triangle with one point in it has a weight that is positive everywhere except on the horizon itself. That essay ended on the case it could not reach. A sky drawn as a dome of triangles has every vertex at infinity. Each vertex is a direction with weight zero, the interpolated weight is zero at every pixel, and the rate-and-weight pair has nothing to divide by. The triangle is a piece of the plane at infinity, and on that plane there are no distances for a rate to be measured along.

It suggested an answer, and the answer holds exactly.

The attribute that belongs to a direction

What a sky texture is looked up by is a direction — which way the pixel looks — and the vertices of a sky triangle are directions. So the natural attribute is the direction itself, three numbers at each vertex.

A sky triangle 60° across with every vertex at infinity: carried as directions its sky grid is exact; carried as angles it is 7.7° outA triangle whose three vertices are all directions, w = 0, spanning 60° of sky and drawn through a matrix with no far plane. The lines of constant azimuth and elevation, every ten degrees, are drawn two ways. Dark: where the direction carried over w as an attribute says they are — the pixel's own ray to 3e-14° in double precision. Light: where a sky looked up by the vertices' azimuth and elevation, carried as values, puts them — up to 7.68° out in azimuth and 3.22° in elevation. The weight that divides a texture's rate is zero at all three vertices and at every pixel; the direction needs no division, only its length thrown away.three vertices at w = 0 · directions exact to 3e-14° · angles off by 7.7°correct from 6 cm, at 160 mm wideno far plane, no point in the triangle
Fig. 1 A triangle of sky 60° across, every vertex a direction, drawn through a matrix with no far plane. Lines of constant azimuth and elevation every 10°: dark where the direction carried over w puts them, exact to 3e-14°; light where the vertices’ angles carried as values put them, up to 7.68° out in azimuth and 3.22° in elevation.

The reason it works is the same linearity the rate used. A direction at a vertex is that vertex’s homogeneous coordinates with the fourth set to zero; the clip-space position is a linear function of them; and the rasteriser’s perspective-correct interpolation — divide each attribute by the vertex’s clip ww, interpolate straight across the page, and divide by the interpolated one-over-ww — is exact for any attribute that is linear in the vertex’s homogeneous coordinates. A texture does not interpolate on the page derived that recipe, and a direction is as linear as a coordinate can be.

The point on the sky a pixel sees is some combination of the three vertex directions — a direction too, with weight zero. Its projection is the pixel. So the interpolated vector, before any final division, is that combination scaled by the interpolated one-over-ww: it points exactly along the pixel’s own ray and has some length. The final division has nothing to divide by, and does not need to happen. The length is simply thrown away.

The figure draws the sky’s grid of azimuth and elevation both ways across a triangle spanning sixty degrees. Through the carried direction, every grid line lands where the pixel’s ray says it should, to 3×10−143\times10^{-14} degrees. Through the vertices’ own azimuth and elevation carried as values — the obvious alternative, if the sky is stored as an equirectangular map — the grid lines are bent and displaced, up to 7.7 degrees in azimuth near the top of the triangle.

Exact at every span, to the format’s digits

A claim of exactness is only worth its measurement, and here the measurement is simple: at every pixel inside the triangle, the angle between the interpolated direction and the ray that pixel looks along.

Carried over w, the direction is the pixel's ray to 5e-14° in double precision and 6e-6° in single, at any spanSky triangles 10, 20, 40, 60, 80, 100° across, every vertex a direction, and at pixels 6 px apart inside each the angle between the interpolated direction and the ray the pixel looks along. In double precision: 2e-14, 1e-14, 2e-14, 3e-14, 2e-14, 5e-14°. In single, the format a graphics processor interpolates in: 4e-6, 4e-6, 5e-6, 5e-6, 5e-6, 6e-6°. The interpolated vector's length is not one — it runs from 1.00 to 2.50 over the widest triangle, set by the interpolated one-over-w — so the length is discarded and only the direction is used, which is exact because a direction is linear in the homogeneous coordinates the rasteriser interpolates.10⁻¹²10⁻⁹10⁻⁶20406080100how much sky the triangle spans, degreesworst angle between the carried direction and the pixel's ray (degrees, log)doublesingleevery pixel 6 px apart inside the trianglethe rounding of the format
Fig. 2 Sky triangles 10° to 100° across, the angle between the carried direction and each pixel’s ray at pixels 6 px apart. Double precision: 1e-14 to 5e-14°. Single: 4e-6 to 6e-6°. The carried vector’s length runs from 1.00 to 2.50 over the widest triangle, so only its direction is used.

In double precision the direction is off by between one and five parts in 101410^{14} of a degree, at every span from ten degrees to a hundred. That is rounding and nothing else: the error does not grow with the triangle, because nothing in the interpolation approximates. In single precision — the format a graphics processor interpolates in — it is four to six millionths of a degree, which is single precision’s own resolution on a unit vector, about 6×10−86\times10^{-8} radians.

That is the same answer the ground triangle gave. A point at infinity is an ordinary vertex found that the projection matrix draws a direction through the same multiplication as a point, and the rate essay found that a vertex at infinity is the best-conditioned vertex in the triangle, because its ww is a depth rate of order one rather than a depth of thousands of metres. A sky triangle is made of nothing but such vertices, and it is correspondingly well behaved.

The length that is thrown away

The one thing the direction does not give for free is a unit length, and it is worth seeing how far from one it is.

The carried vector's length runs from 1.00 to 1.64 across an 80° sky triangle, and only its direction is usedThe length of the direction carried over w, at every pixel 9 px apart inside an 80° sky triangle: from 1.000 to 1.643, shaded dark to light. It is set by the interpolated one-over-w of the triangle's three vertices — at each vertex it is exactly one over that direction's depth rate — which is what a texture's rate would be divided by if the weight were not zero. A sky looked up by the direction normalises it away; a sky looked up by the raw vector, or by a coordinate computed from it before normalising, inherits this shading as an error.the carried vector's length, 1.00 dark to 1.64 lightcorrect from 6 cm, at 160 mm widean 80° sky triangle
Fig. 3 The length of the direction carried over w at pixels 9 px apart inside an 80° sky triangle, from 1.000 dark to 1.643 light. At each vertex it is one over that direction’s depth rate; between, it is set by the interpolated one-over-w.

Across an eighty-degree triangle the interpolated vector’s length runs from 1.00 to 1.64, shaded dark to light: one at the vertex looking straight ahead, larger toward the vertices the camera sees obliquely, whose clip ww — their depth rate, the component along the view — is smaller. The length is exactly the quantity a texture’s rate would have been divided by, if the weight had not been zero; on a sky triangle it carries no information about the sky and has to be removed.

That is the practical warning. A sky looked up by the normalised direction — a cube map, which is indexed by direction and never by length — is exact. A sky looked up by some coordinate computed from the raw vector before normalising inherits the shading in the figure as an error: a lookup that takes the vector’s components as if they were unit, or a fog term computed from its length, is off by up to two-thirds across a wide triangle. Normalise first, and everything that follows is exact.

Angles are not linear in anything the pipeline carries

The obvious alternative — carrying each vertex’s azimuth and elevation and looking the sky up by the interpolated angles — fails for a reason that is the direction’s success turned inside out.

The angle lookup's error grows as the 2.4 power of the triangle's span: a 4096-texel sky needs triangles under 10° to stay within a texelThe worst error, in degrees, of a sky looked up by azimuth and elevation carried as values, over triangles 5, 10, 20, 40, 60, 80° across: 0.0180, 0.0978, 0.5269, 2.7304, 7.6842, 19.8815°. The error grows as the 2.40 power of the span — faster than the square, which is what the second-order part of an angle would give alone, because the wider triangles reach higher toward the zenith, where azimuth curves fastest. The level lines are one texel of an equirectangular sky 1024, 4096, 16384 texels round: 0.352, 0.088, 0.022°. To stay within a texel, the sky must be cut into triangles no wider than 17.0°, 9.6°, 5.4° respectively; carried as directions it needs no cutting at all.510204060800.1110how much sky the triangle spans (degrees, log)worst error of the angle lookup (degrees, log)a texel of 1024a texel of 4096a texel of 16384worst error over the triangle, azimuth or elevationslope 2.40
Fig. 4 The worst error of the angle lookup over triangles 5° to 80° across: 0.018° at 5°, 0.098° at 10°, 2.73° at 40°, 19.9° at 80°, growing as the 2.4 power of the span. One texel of an equirectangular sky is 0.352°, 0.088° and 0.022° at 1024, 4096 and 16384 texels round: to stay within a texel, triangles no wider than 17°, 9.6° and 5.4°.

An angle is a nonlinear function of a direction — an arctangent of two of its components — and perspective-correct interpolation reproduces exactly only the functions that are linear. The angles at the three vertices are right, and the angles in between are the interpolation of three right values through the wrong function. The error is small near the vertices and largest in the interior, and it grows fast with the triangle’s size: 0.018 degrees for a triangle five degrees across, 0.098 at ten, 2.7 at forty and nearly twenty at eighty. Over the middle of that range the error grows as about the 2.4 power of the span — faster than the square, which is what the second-order part of an angle would give on its own, because the wider triangles reach higher toward the zenith, where azimuth changes fastest for a given change of direction.

In the currency a sky is stored in, that is a budget for tessellation. One texel of an equirectangular sky 4096 texels round is 0.088 degrees. To keep the angle lookup within a texel, the sky must be cut into triangles no wider than 9.6 degrees; at 16,384 texels, 5.4 degrees. A dome of such triangles is a few hundred to a few thousand of them for a sky that the direction attribute draws exactly with a handful.

The seam where azimuth wraps

A stored azimuth has a seam, and the angle lookup meets it.

Across the seam where azimuth wraps, the angle lookup is 178° out and the direction is still exactA 40° sky triangle centred at azimuth 150° and the same triangle moved to straddle 180°, where an equirectangular sky's azimuth runs from 180 to −180. Carried as directions: 2e-14° and 2e-14°. Carried as angles: 2.73° away from the seam and 177.8° across it, because a vertex at 160° and a vertex at −160° interpolate through zero, the long way round, and the triangle is painted with the opposite sky. A direction has no seam: it is the pole and the wrap alike, because neither is a place in the vector.40° triangle at azimuth 150°: directions2e-14°40° triangle at azimuth 150°: angles2.73°the same across 180°: directions2e-14°the same across 180°: angles178°bars on a log scale from 1e-16°a direction has no seam
Fig. 5 A 40° sky triangle at azimuth 150° and the same triangle across 180°, where a stored azimuth wraps from 180 to −180. Carried as directions: 2e-14° both. Carried as angles: 2.73° away from the seam and 177.8° across it — the triangle painted with the opposite sky.

Move the same forty-degree triangle from azimuth 150 degrees to straddle 180, where an equirectangular map’s azimuth runs from 180 back to minus 180. A vertex stored at 160 degrees and a vertex stored at minus 160 are twenty degrees apart on the sky, and three hundred and forty apart as numbers. Interpolated as numbers, the azimuth between them runs through zero — the long way round — and the triangle is painted with the sky from behind the viewer, 178 degrees out. The direction does not notice: 2×10−142\times10^{-14} degrees on both sides of the seam.

Renderers that store skies as angles handle the seam by duplicating vertices along it, each copy carrying the azimuth that suits its triangle, and handle the pole — where every azimuth meets — by splitting the triangles that touch it. Neither repair is needed for a direction. The seam and the pole are places in a parametrisation of the sky, and a direction is not a parametrisation; it is the sky.

The pole is the seam’s companion and fails the same way for the same reason. A triangle with a vertex at the zenith gives that vertex an azimuth, and any azimuth is as good as any other there: the stored value is arbitrary, and whatever is chosen is interpolated toward the other two vertices as if it meant something. The triangle’s interior near the pole is then painted with a fan of azimuths that belongs to no direction the pixels see. The widest triangles in the span figure are the ones that climb toward the zenith, which is where their extra error beyond the square comes from; a triangle that reaches the pole itself has an error set by an arbitrary choice rather than by its size.

Why the storage decides the error

The two lookups differ in where the sky’s parametrisation is applied, and the difference is the whole of the error. The direction lookup interpolates the one quantity the pipeline interpolates exactly and applies the parametrisation afterwards, at each pixel, to a direction that is already right. The angle lookup applies the parametrisation first, at the vertices, and then asks the interpolator to carry the result across the triangle — which it can do exactly only if the parametrisation is linear, and no map from directions to a flat rectangle is.

That is a statement about every stored sky, not only the equirectangular one. Six flat pictures of everything describes the cube map: six pinhole pictures on the faces of a cube, each a projective map from directions to its face. Within one face that map is linear in homogeneous coordinates, which is why a cube map can be indexed by a direction with no approximation — the lookup divides by the largest component and reads the face. What a 360-degree photograph actually is describes the equirectangular map, whose two coordinates are angles, and the kink at a seam the price a panorama pays where one face or strip hands over to the next. A sky carried as a direction is looked up the same way whatever its storage, and each storage’s own seams and poles are met pixel by pixel, where they cost nothing, rather than vertex by vertex, where they cost a triangle.

It also says what tessellation is for. A dome cut into triangles five degrees wide is not needed to draw the sky’s shape — a sky has no shape, only directions — but only to keep the angle lookup’s error within a texel. Carried as a direction, the sky can be a single enormous triangle, or the far plane of a frustum, or a quadrilateral covering the screen with its four corner rays as attributes; the last is how most renderers draw it, and it is exact for the reason measured here.

The far plane was never needed

A sky triangle is drawn through the same matrix the ground triangle was, the one with no far plane. One plane is nearly free found that removing the far plane costs the depth buffer almost nothing, and the divide is postponed, not avoided that the pipeline’s division by depth happens once, late. Both matter more for a sky than for a ground.

A renderer with a finite far plane cannot draw a vertex at infinity at all — every finite far plane clips it — so a sky is usually drawn as a large sphere at some finite radius, just inside the far plane. That sphere’s triangles have finite depths, a texture coordinate at each vertex, and every one of the problems measured here: a sphere drawn with per-vertex angles is a sphere looked up by the angle lookup. Drawn at infinity, the same sky needs no radius, no depth range, and no angles; its depth after the divide is exactly one, the far end of the buffer, and every opaque surface in the scene is in front of it by construction.

Where the rate and the direction meet

The two attributes, the ground’s rate and the sky’s direction, are one idea applied at two places on the plane at infinity.

Carried as a rate, the texture is exact to the horizon; carried as a value, it is wrong by 4 checks or moreThe texture coordinate down the principal column, rows 200 px to 0.5 px below the horizon, against the ground's own. With the far vertices given their texture as a rate and a weight of 0, interpolated over w and divided once, it is off by 3e-15, 1e-14, 1e-14, 2e-13, 1e-12, 5e-12, 3e-11, 2e-10, 6e-10 checks in double precision — the rounding of the arithmetic, growing only because the coordinate itself grows to 2443 checks at the last row. In single precision the same interpolation is off by 2e-8 to 2e-4 checks, a part in ten million of the coordinate. Given instead the coordinate of a point 20 m along each direction as a value, with a weight of 1 — what an attribute at an ordinary vertex would be — the rasteriser reads the direction's depth rate as a depth and the texture is off by 6, 7, 4, 40, 102, 224, 591, 1204, 2428 checks.0.512510205010020010⁻¹⁵10⁻¹²10⁻⁹10⁻⁶0.00111000pixels below the horizon (log scale)error in the texture coordinate, checks (log)rate, doublerate, singlestand-in value1 m checks · principal columnrate: 6e-10 at worst
Fig. 6 The earlier measurement, for comparison: the ground’s texture coordinate down the principal column, carried as a rate with a weight of zero at the far corners, exact to 3e-15 checks in double precision and a part in ten million in single; given the far corners a value instead, off by whole checks.

The ground triangle had one vertex that was a point, and its texture needed the point’s value, the directions’ rates and a weight to divide by. The sky triangle has none, and its attribute needs no division. In both, the attribute is linear in the vertex’s homogeneous coordinates, and that is the entire condition for exactness. What differs is what the attribute is: on the ground, a coordinate that grows without bound toward the horizon, whose value at infinity is a rate; on the sky, a direction, whose value at infinity is itself.

A scene with both is a ground of rate triangles and a dome of direction triangles, and the two meet along the horizon. A direction lying exactly on the horizon is a vertex of both: as a ground vertex it carries a rate and a weight of zero, as a sky vertex it carries itself. Where parallel lines meet is the reminder that the horizon is a line in the picture and not an edge in the scene, and nothing about either triangle changes there except which attribute has meaning.

What was assumed

The sky is looked up by direction. A cube map, or a direction-indexed function, is exact under the direction attribute. An equirectangular sky can still be used — the angles are computed from the normalised direction at each pixel rather than interpolated — and that per-pixel computation is exact; what fails is interpolating angles computed only at the vertices.

No vertex lies behind the eye. A sky triangle wider than the field of view, with a vertex behind the camera, has a negative clip ww at that vertex and must be clipped first, in clip space, with its direction attribute carried through the clipper’s interpolation like any other. The rate essay found that a clipper which drops the weight breaks the ground’s texture; a clipper that carries the direction as three plain attributes is correct automatically, since the direction needs no weight.

The camera does not move between vertices. Every direction here is seen from one eye. A sky seen from an eye that has moved is the same sky — directions do not have parallax — which is why a sky is drawn without translation, and why a direction attribute needs no knowledge of where the eye is.

Still open: whether the ground and the sky meet without a crack

The meeting described in the section before last has not been measured. A ground triangle and a sky triangle that share an edge along the horizon share two vertices, both directions with ww equal to their depth rates, and a rasteriser decides which pixels along that edge belong to which triangle by the same edge function computed from the same vertex positions. In exact arithmetic the two triangles tile the edge with no gap and no overlap. In single precision the vertices’ pixel positions are rounded, and a vertex at infinity reaches the page through a division by its depth rate like any other.

The measurement that settles it draws a ground of rate triangles and a dome of direction triangles sharing the horizon, rasterises both with the vertex positions rounded as a graphics processor rounds them, and counts the pixels along the horizon covered by neither triangle or by both — and, for the pixels the ground triangle claims within a fraction of a pixel of the horizon, how large the texture coordinate becomes as the interpolated weight approaches zero. If no pixel is lost or doubled and the ground’s coordinate stays finite on every pixel it owns, the horizon is only a place where one attribute hands over to another; if pixels fall into a crack, the horizon is a seam of its own that a renderer must close.

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.

Clip spaceCube mapEquirectangularHomogeneous coordinatesPlane at infinitypoint at infinityProjective map