A ground and a sky share an elevation, not a distance
Worth reading first: The divide is postponed, not avoided · A texture does not interpolate on the page.
Ground and sky meet at the horizon without a crack put a ground of triangles and a sky of triangles against one another along a row of shared horizon vertices, rasterised them as a graphics processor does — positions snapped to a fixed-point grid, every pixel given to the one triangle whose edges hold its centre — and found no pixel lost and none claimed twice, at any roll and any snapping. The two sides met exactly along their edge. What they carried did not match: the ground carries its texture as a rate and a weight, the second falling to zero at the horizon, and the sky carries a direction. A texture meant to run continuously from one into the other — a haze, a fog band, a gradient painted across the horizon — has to be computed separately on each side.
The essay ended by asking whether the two sides could at least agree on something they both know how to compute. Both know the elevation of a pixel’s ray above the horizon: the ground through its own geometry, the sky directly. If that agrees across the edge, a haze that depends only on elevation can be painted across the horizon by two different attributes without a seam.
It agrees, to the snapping. And the quantity a fog is usually written in, distance, turns out not to be one the two sides could ever share.
The elevation each side reports
The ground’s attribute at a pixel is a rate and a weight, interpolated over the triangle in the way that makes their ratio the ground point the pixel sees — the divide is postponed, and a texture does not interpolate on the page unless it is carried this way. The ray through that pixel meets the ground at a distance, and its elevation is a depression: the negative of the angle whose tangent is the eye’s height over that distance. Written that way it divides by the distance, which grows without bound near the horizon; written with the weight on top — the eye’s height times the weight, over the length of the rate — it goes smoothly to zero as the weight does, and needs no special case at the edge. The sky’s attribute is a direction, carried the same way over the triangle, and its elevation is simply the angle of that direction above the level.
The scene is the earlier essay’s: an eye 1.62 metres up, 740 pixels of focal length, the camera rolled three degrees, the horizon a row of level directions shared by fourteen ground quads and fourteen sky quads, every position snapped to 1/256 of a pixel. For every pixel within six rows of the horizon, the figure takes the elevation its owner’s attribute reports and subtracts the true elevation of the ray through that pixel’s centre.
Every pixel on both sides reports its ray’s elevation to within 2.19 millionths of a radian. A pixel is about 1,350 millionths. The two sides of the edge agree, pixel by pixel, to about a six-hundredth of a pixel, and there is nothing at the horizon that a quantity computed from elevation could step across.
The error is not the arithmetic’s. Single precision gives the same 2.19 as double — unlike the depth that the precision a depth buffer has left measured running out near the far plane: the weight is small near the horizon, but it multiplies the eye’s height and divides nothing, so its relative rounding stays at single precision’s one part in sixteen million and the elevation’s absolute error stays near a ten-millionth of a radian. The error comes from the snapping. Every vertex position was moved by up to half a grid step before the triangle was drawn, and the attributes were computed for the unmoved vertices, so every pixel’s attribute belongs to a ray a little off its own; the dashed band is half the grid’s diagonal over the focal length, and every point lies inside it. The slider shows the proportion: at 1/16 of a pixel the band is sixteen times wider and the worst error 32 millionths of a radian; at 1/65,536 of a pixel the band is 256 times narrower and the worst error nine thousand-millionths.
So the earlier essay’s question has a clean answer. A haze whose strength depends only on the elevation of the ray — how much air a line of sight passes through, for an atmosphere of fixed depth, depends on nothing else — can be computed from the ground’s rate and weight below the horizon and from the sky’s direction above it, and the two agree at the edge to the snapping. The rasteriser does not make a seam, and the attributes do not either.
Why the weight belongs on top
How the ground’s elevation is written matters at exactly one kind of pixel, and the earlier essay found that kind. A pixel whose centre falls precisely on the snapped horizon edge goes, by the top-left rule, to one side or the other, and for some rolls it goes to the ground — with an interpolated weight of exactly zero, since the weight is zero along the whole of that edge. Written as the depression of a ground point at distance rate over weight, that pixel’s elevation is a division by zero: its distance is infinite, or, in floating point, an infinity whose arc-tangent happens to come out right, or a zero over zero that does not.
Written with the weight on top, the eye’s height times the weight over the rate’s length, the same pixel’s elevation is the arc-tangent of zero over a finite number: exactly zero, the elevation of the horizon, which is where its centre is. Nothing downstream needs to know that the pixel was a special case. A haze that reads the ground’s elevation this way can be evaluated on every ground pixel the rasteriser produces, including the ones that sit on the edge, and the same form gives the sky’s side nothing to reconcile.
Fog by distance is a different quantity
The fog most renderers draw is not written in elevation. It is written in distance: a point D metres away is hidden by a share one minus e to the minus D over L of the view, with L the visibility. The ground knows its distance, from its rate and weight. The sky is at infinity and fully fogged. At the horizon the ground’s distance runs off to infinity too, so the fog ought to approach one from the ground’s side as it does from the sky’s.
It does approach one. It does it in less than a pixel.
A ray a row below the horizon meets the ground about metres out — the focal length times the eye’s height over the rows below the horizon — which is 1,199 metres for this eye and this lens. So the ground a single row below the horizon is only a kilometre and a fifth away. At three hundred metres of visibility that is well fogged, 0.98, and the step to the sky’s 1 is invisible. At twenty kilometres it is fogged 0.06, nearly clear, and all the rest of the rise to 1 happens between that row’s centre and the horizon itself: the ground’s distance passes twenty kilometres only in the last 0.06 of a pixel.
The fog is a continuous function of the elevation, as every quantity the ground carries is. What it is not is a function a pixel can follow. Its rise from the ground’s value a row down to the sky’s is spread over the rows in which the ground’s distance runs from about L to infinity, and there are of them. When the visibility is shorter than , about 1,200 metres here, the rise takes more than a row and the rasteriser draws it. When the visibility is longer, the rise lives inside the last row, and the row samples it once.
The step, measured along the horizon
The figure above follows one column. Along the whole horizon the rolled camera puts every column’s last ground pixel at a different depth below the edge, and the step from the last ground pixel to the first sky pixel varies with it.
Sampled at pixel centres, as a fragment shader samples, the step is a tenth at a kilometre of visibility, a third at three, two-thirds at ten and 0.85 at thirty. A horizon drawn under a thin haze has a sharp line along it: ground a kilometre away and mostly clear, sky fully fogged, nothing between.
The obvious repair is the one a pixel is not a point always prescribes: a pixel is an area, so average the fog over the area rather than sampling it at the centre. The dashed curve does that, with sixty-four points spread over each pixel’s square and each point’s fog read from whichever side owns it. It helps less than one might expect. At thirty kilometres the step falls from 0.85 to 0.69, at a hundred from 0.94 to 0.73. The last ground row’s pixels are mostly ground a kilometre or two away, and averaging over them does not change that; it only lets the sliver of sky and the sliver of very distant ground at the top of the pixel contribute their share.
And the averaged value is the right one. A camera photographing a flat plain under twenty kilometres of haze records exactly this: the ground a pixel below its horizon really is a kilometre away and really is nearly clear, and the sky above it really is the full brightness of the haze. That is why a real horizon is visible through haze at all. The step in the rendered picture is not a fault of the shared edge or of the attributes; it is the picture being right about distance, which jumps from a kilometre to infinity in one row.
Where the step goes away
The step depends on how much distance the last row holds, and that is set by the eye’s height as much as by the lens.
From a standing eye at ten kilometres of visibility the step is 0.59, and the fog’s rise to the sky fits inside an eighth of a row. From five metres up — an upper window — it is 0.38. From fifteen metres the rise spans a whole row and the step falls to 0.13; from fifty it is under a hundredth; from a hundred and fifty metres, an aircraft on approach, the rise spreads over eleven rows and there is no step at all. A flight simulator’s horizon hazes continuously and a walker’s does not, and the reason is one ratio: the eye’s height times the focal length, over the visibility.
The saw-tooth along a rolled horizon
There is one artefact in the rendered step that a camera would not record, and it is the one a viewer notices first in motion.
With the camera rolled three degrees the horizon crosses a row of pixels every nineteen columns, and each crossing changes which pixel is the last ground pixel in its column. Just after a crossing, the last ground pixel’s centre is almost a whole row below the horizon, and it sees ground 1.2 kilometres away; just before the next crossing, its centre grazes the edge and sees ground arbitrarily far away. So the last row’s fog runs in a saw-tooth along the horizon, rising towards one and dropping back once every nineteen columns. Sampled at centres it jumps by 0.082 a column on average; averaged over each pixel, where the sky’s share of the pixel rises as the pixel’s own fog falls, by 0.061.
Both saw-tooth, because both are honest about each pixel’s own ground. What the averaged one removes is the spike when a centre nearly touches the edge, where a centre-sampled pixel reports a ground a hundred kilometres away that fills a thousandth of it. In a moving picture those spikes are the shimmer along a hazy horizon as the camera rolls. They are the horizon’s version of the texture sparkle a texture reaches the horizon as a rate described, and they have the same remedy: sample the area, not the point.
What the edge shares
Put together, the two sides of a rasterised horizon share their edge exactly, as the earlier essay found, and they share the elevation of their pixels’ rays to the snapping, as this one finds. Any quantity that depends only on the direction of the ray — elevation, azimuth, the angle to the sun, the length of air a line of sight crosses in an atmosphere of fixed depth — can be computed from the ground’s rate and weight below the horizon and from the sky’s direction above it, and painted across the edge without a seam. A sky is carried as a direction was the half of this that made the sky’s side possible; a point at infinity is an ordinary vertex was the half that let the ground reach the same edge.
What they cannot share is anything that depends on distance, because the ground’s distance a row below the horizon is finite and small, and the sky’s is not. A renderer that wants a fog to meet its sky has two honest choices. It can write the fog in elevation, which is physically right for atmospheric haze seen from low down and meets the sky exactly; or it can write it in distance, which is right for a ground fog, keep the step, and average each pixel’s area so that the step is the one a camera would record rather than a shimmering one.
What was assumed
The ground is flat and level. The elevation of a ground point is read from its distance by the eye’s height, which is true only for a level plane. A ground that rises towards the horizon — a hillside, the curve of the earth over tens of kilometres — has a horizon of its own below the level one, and the sky and the ground then do not share an edge at all; what fills the gap is the subject of the ground stopped short figure of the earlier essay.
Fog is a simple exponential of distance. Real atmospheric scattering depends on the density of the air along the line of sight, which thins with height, so a line of sight’s optical depth is a function of its elevation and its length together. The pure-elevation haze and the pure-distance fog are the two ends of that, and a physically based sky model lies between them.
The pixel’s area is its square. A camera’s pixel integrates over a footprint set by its lens and its sensor, not a square, and a renderer’s anti-aliasing filter is wider than a square. Either spreads the horizon’s step over more than one row, which would soften it further than the averages here.
The attributes are exact at the vertices. They are computed in double precision for the unsnapped vertices and then interpolated over the snapped triangles. A renderer that computes them from the snapped positions would have attributes consistent with its own triangles, and its elevation errors would be the arithmetic’s alone — about a ten-millionth of a radian in single precision.
Still open: whether an elevation haze and a distance fog can be blended across the edge
The two honest choices above are ends of a range, and a real scene wants both: a ground fog that thickens with distance near the eye, and an atmospheric haze that depends on the line of sight’s elevation far away.
The measurement that settles whether they can be combined without a step writes the fog as a blend — distance-based below some angle below the horizon, elevation-based above it, the two joined smoothly over a band of rows — and asks how wide the band has to be before the combined fog’s step at the horizon, averaged over each pixel, is smaller than the least step a viewer can see, and how far the blend then departs from the true distance fog on ground a few hundred metres away. If a band of two or three rows is enough, a renderer can have a physically honest fog near the eye and a seamless horizon at once; if it needs tens of rows, the choice between them is real and has to be made per scene.
Shares its objects with
Essays that name at least two of the same things, and that neither author linked.
- What happens behind the eye — both name clip space, homogeneous coordinates, point at infinity, projective map
- A point and a line are one object — both name homogeneous coordinates, point at infinity, projective map
- An angle is a cross-ratio — both name homogeneous coordinates, horizon, point at infinity
- The conic a circle becomes — both name horizon, point at infinity, projective map
- Three conics are one conic and a choice of horizon — both name horizon, point at infinity, projective map
- Two lines at infinity — both name horizon, point at infinity, projective map
Named objects
A flat tag is an object no other essay names yet.
Clip spaceHomogeneous coordinatesHorizonpoint at infinityProjective mapSampling grid