Titanium Sky
I went into EverQuest’s 2005 client expecting to find a skybox: a textured cube or dome, drawn last, with some scrolling clouds on it. That is not what is in there. What is in there is a small stack of pieces, each of them plainer than you would guess, and several of them doing a job other than the one they appear to be doing.
Here is the first, and it is the one that changes how you read the rest.
In this client, the thing that makes distant terrain disappear is not the fog, and it is not the far clip plane. It is the sky.
Everything below is the same move repeated: take one piece, ask what it is actually made of, and find that the answer is both smaller than expected and load-bearing somewhere else. None of it is clever. It is 2005, the budget is nothing, and the parts are chosen so that the cheap thing and the correct thing are the same thing.
The dome is a sphere, and it is the world’s far plane
The sky dome is a closed sphere of 962 vertices centred on the camera. It is opaque. It writes depth. And its radius is the fog end — the distance at which the world is meant to have faded out entirely.
Those three facts together mean the dome is not scenery. It is the culling system. It is drawn first, before anything else in the frame, and because it writes depth at its own radius it stamps that distance across every pixel of the screen. When the world is drawn afterwards, every fragment further away than the dome fails the depth test. Not because a far clip plane rejected it — the actual projection’s far plane sits far beyond, and never comes into it — but because there is already an opaque sphere sitting at that distance with its depth recorded.
The sky is what removes distant geometry from the frame. Turn the fog end down and the world does not fade; it is cut, at a fixed radius, by a sphere.
That is easy to assert, so here it is, and the control is one a player had. The view-distance slider interpolates the fog end between the bounds the zone ships with — for East Karana, 600 at the bottom and 1200 at the top — and touches nothing else. At the top the stable across the field is visible and already hazy. Drag it to the bottom and the stable is gone, and the far pine is halfway to dissolving.




East Karana's own environment: fog from 410 to 1200, faintly blue. The view-distance slider is at 15, the top of its range, so FOGEND is the far end of the zone's clip pair. The stable is visible and already hazy.
The third panel is the one that says what the dome is doing, and it is a hypothetical — this client never draws it. Take the dome away, leave everything else where it is, and the stable comes back. The far bound is a per-node test and that geometry survived it; node bounds are coarse, and the node holding the stable reaches nearer than the boundary. Nothing removed it. What would have removed it is the depth buffer, per fragment, because an opaque sphere had already written itself there.
This is also why the dome has to be drawn first. The usual arrangement is the other way round, and Quake III is a good example of how deliberate that usually is: its sky is clipped to the surfaces a mapper flagged as sky, then drawn at a depth range pinned to the far end of the buffer, specifically so it cannot occlude anything. It goes last and fills what the world left. This one does the opposite — first, opaque, writing depth — and gets a culling mechanism out of it instead of just a background.
Culling: one number, two mechanisms
One number drives two different tests, and they are not the same shape.
When the fog distance is updated, that value is handed to two places. One is the dome’s radius. The other is the camera’s view distance, which becomes the far plane of the frustum handed to the occlusion culler — a third-party library the client licensed rather than wrote, which we will come back to. Same float, two consumers, and they belong to different machines: one is a CPU library deciding what to submit, the other is the depth buffer deciding what survives.
But a frustum’s far bound is a plane. It rejects a node by comparing view depth. The dome bounds by radial distance — it is a sphere centred on the eye, so a fragment survives exactly when it is nearer than the radius, in any direction.
A plane and a sphere agree only on the view axis. Off-axis they come apart. A point at the very edge of the frame can be past the sphere and still nearer than the plane, because its view depth is its radial distance times the cosine of the angle off-centre. The wedge between the two is real geometry: it passes the node cull, gets submitted, gets transformed, and then every one of its fragments loses to the dome’s depth.
The looser bound is the plane, which is the right way round, and not by accident: because both come from one expression, the node cull cannot be tighter than the dome anywhere. If it were, it would remove geometry the dome would have kept, and you would watch the world disappear at the edges of the screen as you turned. Being loose costs some wasted submission and nothing else.
The node test is a performance optimisation. The dome is the one that decides what you see.
Calling any of this “the far plane” is a simplification that stops working the moment you look at a corner of the screen.
Painting the dome
The dome is 31 azimuth divisions by 29 elevation rings. Each ring carries 32 vertices rather than 31, because the first and last sit at the same azimuth — a duplicated seam vertex, so the ring’s texture coordinates can run cleanly from one end of the map to the other without wrapping. Add a small cap ring near each pole and the two pole vertices themselves and you get 962 vertices and 1,861 triangles.
Every one of those vertices takes its colour from the ColorMap, by row. The row comes from the vertex’s polar angle: the first ring sits a tenth of a radian off the pole and the last a tenth off the antipode, and the 29 rings divide what is left, so the great circle ninety degrees from the pole lands halfway down the file. There is no lighting calculation, no shader doing atmospheric anything. A vertex is looked up in a small image and the rasteriser interpolates between them.
The column is the surprising part: there isn’t one. The dome pins its column at the middle of the map and reads a single ramp. It can do that because the shipped dome ColorMaps barely vary across their columns at all — a few parts in 255 from one side to the other. The texture is two-dimensional; the gradient in it is one-dimensional.
So what is the other axis for? Two things, neither of them the picture. The last column is metadata — four texels that are the sun’s colour, the moon’s colour, the fog colour, and the world’s ambient light. And the bottom two rows are the horizon object’s, which we will get to. The dome’s gradient is the rectangle left over when you exclude both, and it can never reach either by construction.
The pole is the sun
Here is the mechanism that makes one static gradient produce a whole day.
The dome’s pole is not up. It is the direction of the sun. The whole sphere is oriented by the day phase, so row 0 of the ColorMap is wherever the sun happens to be, and the ramp runs from there outward to the antipode.
At noon the sun is overhead, the pole is up, and the ramp is elevation — deep at the zenith, pale at the horizon, exactly like a sky. At dawn the pole is on the eastern horizon, and the same kind of ramp puts its warm end around the rising sun, its middle overhead, and its far end in the still-dark west. One one-dimensional gradient, re-aimed, gives you a sunrise that is orange in one direction and night-blue in the other.
And time of day is not an axis of the texture. It selects the texture. A schedule names a ColorMap file per slot; the sky binds one; nothing downstream of that knows what time it is. Every map is authored for the orientation it will be bound under, which is why the dawn map can be lopsided in a way the noon map is not.
Sun and moon
Two billboards, four vertices each, drawn at a fixed distance from the eye. Their direction comes from the day phase, on an arc with a configurable heading and height — separately configurable per body, which is why the moon does not have to be the sun’s antipode.
They are drawn white. They read no ColorMap at all. Every bit of colour in the sun is in the sun’s own texture, and — this is the nice part — so is every bit of its shape. The disc and the rays are one image, distinguished only by its alpha channel. The billboard is a flat quad; the star shape you see is alpha.
Which is why the roll is visible. Nothing anywhere sets a roll angle for the sun. It falls out of the look-at basis the billboard is built with, and because the rays are a fixed pattern baked into the texture, that basis is legible at a glance: the spikes turn as the sun crosses the sky. A disc would have hidden it. Rays make the billboard’s orientation part of the picture, whether or not that was the intent.
Stars, in two populations
Ten thousand stars, and two hundred more, and they are not the same thing.
The ten thousand are single-pixel points — no texture, no size, one vertex each. The two hundred are textured quads, four vertices each, taking a cell from a four-by-four atlas. Together they are 41,200 vertices submitted in one place, which is a lot of vertices for something you are not supposed to notice.
Positions are generated once, at load, from the client’s own random number generator, and never move. The distribution is not uniform over the sphere — the elevation parameter is used directly rather than through the arcsine that would make it uniform, so stars bunch toward the poles. It is a bug in the sense that it is not what a uniform sphere sampler would do, and it is invisible in the sense that nobody has ever looked at an EverQuest sky and thought the stars were unevenly distributed.
The gate is the alpha channel
This is the part worth stealing.
A star’s alpha is the product of three numbers: the alpha of the ColorMap texel it reads, an entry from a twinkle table, and the star’s own stored alpha. The day maps have alpha zero everywhere. The night maps have alpha 255.
That is the entire day/night transition. There is no branch. Nothing tests the hour. Nothing decides whether to submit the starfield. All 41,200 vertices are transformed and rasterised at noon exactly as they are at midnight, and every one of them multiplies out to nothing because a texel in a small image says so. The sky switches its own stars off, in the same file that says what colour it is.
Which two texels a star reads is decided by the same two random numbers that placed it — its position and its palette entry come out of one draw.
The twinkle table
Brightness over time is a fixed table of sixty entries:
T[i] = (int)(sin(i * pi / 60) * 240 + 15)
Sixty entries at a step of pi/60 is half a period, not a whole one. The table runs from 15 up to 255 at its middle and back down to 27, and then wraps. So a star’s brightness is one pulse, repeated: it never inverts and it never goes out. The floor matters as much as the peak — at its dimmest a star is still faintly there, which is why the field shimmers rather than blinking.
The index is a clock in tenths of a second plus the star’s own ordinal, modulo sixty. A full cycle is six seconds. Adjacent stars sit at adjacent phases of the same curve, so ten thousand of them move independently with no per-star state and nothing random happening at all.
The two hundred quads are gated by the same ColorMap alpha and are deliberately not twinkled. They sit steady at full alpha while the ten thousand flicker around them, which is what makes them read as the bright ones.
Clouds are meshes, not a shader effect
Each cloud sprite is a grid of 9 by 6 vertices — 54 of them — indexed into 80 flat triangles and drawn in one call. It is a mesh. The soft edges and the wisps are the texture’s alpha; the geometry is a patch.
The patch lives on a sphere of radius 100 whose centre is 98 units below the eye. Those two numbers are the whole trick. They put the eye inside the sphere, two units below its top, so the cloud surface is nearly touching you at the zenith and stretches away toward the horizon. A sprite of nominal size 0.1 covers a few hundredths of a radian of that sphere and can still span a large piece of the screen overhead, while the same sprite near the horizon is a sliver. The deck occupies a narrow band of the sphere’s parameter and the whole of your view.
That near-tangency is also why the flatness matters. Four points on a sphere are not coplanar, and between the 54 vertices the hardware rasterises flat triangles. Near the zenith one quad covers a small patch of sphere and a lot of pixels, so the difference between the mesh and the surface it was sampled from is a couple of pixels wide. Down at the horizon it goes to zero.


Sprites are spawned on a timer and retired on one: nothing for the first second, then one every three to seven seconds, each fading in over five and out over five, up to a per-layer count that tops out at sixteen. A weather pattern can name up to four layers. The consequence is that one frame after a zone loads the sky is genuinely bare, and fills in over the following minute — a sprite’s position is its velocity times its age, so at zero elapsed time there is nothing to see.
The tint is per vertex: 54 ColorMap samples per sprite, interpolated across the triangles. And unlike the dome, the clouds use both axes of the map — row and column both come from where the sprite sits. The one thing in the sky that reads the ColorMap as a two-dimensional image is the clouds.
The horizon object, which is mostly a floor
There is a 65-vertex object at radius 5 around the camera, and it is much the largest thing in the sky.
Thirty-one of its 93 triangles are a fan. From inside, that fan fills the entire lower hemisphere with the fog colour, at full alpha. The remaining 62 are two rings carrying one ColorMap row — row 30, one of the two the dome cannot reach — laid in a band just above the fan’s rim.
So the sky does not stop at the horizon and hand over to the ground. It paints the ground’s half of the screen in the fog colour first, and the world is drawn on top of that. Wherever the world is nearer than the dome, you see the world. Wherever it has been culled, you see fog colour — and fog colour is exactly what terrain at that distance would have faded to anyway.
That is what makes the horizon read as distance rather than as an edge. The seam between the furthest surviving terrain and the sky is filled with the colour the terrain was heading toward. There is nothing to notice, which is the point.
The fan’s colour comes from the device’s fog register, and the fog register was loaded from a ColorMap texel — row 2 of the metadata column. One hop, and we are back at the same image.
Putting it back together
That is all six. A sphere coloured by one ramp, ten thousand points and two hundred quads gated by an alpha channel, two white billboards, some grids on a sphere you are standing inside, and a fan of fog colour under the horizon.
Here they are stacking up, one at a time, against the part of the image each one reads. Drag the pass slider forward and the sky assembles; drag the hour and the whole day moves — the same six mechanisms, the same file, a different picture.


Nothing in that sequence is new by the time you reach it. The point is what happens when you run them in that order and only that order.
Draw order, and why it is the whole illusion
The order is: dome, then stars, then the sun and moon, then the cloud layers, then the horizon object. Then the world.




The dispatcher's own order: dome, stars, satellites, the ring and the cloud sprites, then the horizon — and only then the world.
Both panels are the real renderer at the same pose, and they start identical. Moving a pass is a change to the renderer’s own composite chain, not to a drawing of it.
Two facts make that order load-bearing, and they are both about depth.
The first is that the dome is the only sky pass that writes depth. Everything after it composites without leaving a mark.
The second is that every other sky pass lives in a bubble around the camera. The starfield’s world matrix is five times the identity. The satellites are at distance 10. The cloud sphere has radius 100. The horizon object is at radius 5. Not one of them is within two orders of magnitude of the dome’s radius — they are all sitting on top of you, drawn in front of a sphere at the edge of the world, and the depth test passes trivially for every one of them.
Which raises the obvious objection. If the cloud sphere is at radius 100 and a tree is at 300, why do you not see clouds through the tree?
Because being nearer decides nothing here — nothing ever compares the cloud to the tree. The sky is issued first and, after the dome, writes no depth at all. The world is issued last, with depth writes on, and each of its fragments is tested against exactly one thing: the dome’s stamp at 1525. Survive that and you overwrite whatever colour the sky left in the pixel, clouds included. The cloud geometry really is nearer to your eye than the tree, and it loses anyway, because the comparison never happens.
That is what the sky is: an opaque shell at the culling distance, with a metre or two of decorations painted over it, and then the world painted over that.
Once you see it that way the failure modes are obvious. Move the stars before the dome and the dome — opaque, depth-writing — erases them. Move the clouds before the stars and stars shine through the clouds, because the starfield’s depth test is set to Always and it does not care what is in front of it. Move the horizon object after the world and its fan, which fills the lower hemisphere at full alpha, wipes the terrain off the screen.
And move the world to the front, which is the mistake with the most instructive consequences. Now every sky pass is drawn after it — and every one of them is at radius 5 to 100, nearer than any terrain worth mentioning, with depth writes off. They all pass. The sky paints straight over the ground. Stars land on hills, the horizon fan swallows the landscape whole, and it becomes very clear that the world is not behind the sky. The world is in front of it.
Where this sits
None of this is unprecedented, and it is worth naming what it is an instance of. These are comparisons with published work rather than claims of lineage — nobody involved ever said where any of it came from.
A table instead of an integral. An analytic daylight model was available and
had been for six years when this client shipped: Preetham, Shirley and Smits gave
a closed form for sky luminance and chromaticity at SIGGRAPH ’99, parameterised
by sun position and turbidity. It is a handful of exponentials per sample. A
32x32 table indexed by elevation is what you reach for when you cannot afford
that per vertex — never mind per pixel — on the cards this had to run on. The
price is that the sky’s appearance has to be authored rather than derived,
which is why there is a .ini file deciding what the weather is.
Clouds, the other way round. Quake solved its sky with scrolling texture layers — a 256x128 image read as two 128x128 halves, a solid background and a foreground where palette index 0 is transparent, drifting at different rates for parallax. Titanium does not do that. Its clouds are geometry with per-vertex colour, which is the opposite trade: more vertices, no per-pixel work, and cloud shape that lives in a mesh instead of in a texture’s alpha.
Visibility, bought rather than built. The client does not implement culling.
It licenses it. Every world category — BSP regions, terrain tiles, actors, lights
— is resolved by dPVS, occlusion-culling middleware that ships as its own
160 KB DLL in the game directory. The graphics DLL registers each drawable as a
DPVS::Object, calls resolveVisibility once a frame, and takes the visible set
back through a callback. Nothing dPVS does not report reaches a draw list. They
took 38 of its 140 exports: no portals, no oriented bounding boxes, one cell
holding an entire zone.
The contrast with Quake III is not that one culls and the other does not. It is
when. Quake III precomputes: the map compiler cuts the world into convex leaves,
groups them into clusters, and writes a cluster-to-cluster visibility bit vector
into the BSP — the notoriously slow vis stage. At runtime it finds the camera’s
cluster and tests one bit per leaf, which is about as cheap as a visibility test
can be.
That trade is superb, and it assumes enclosure. Precomputed visibility pays off when walls mean most of the map cannot see most of the map. In an open zone with long sightlines, nearly every cluster can see nearly every other, the bit vector fills in, and the expensive compile has bought nothing.
EverQuest’s zones are all that case. So the work moves to runtime, into a library
somebody else wrote — and the far bound stops being an optimisation and becomes
the thing that makes an outdoor zone finite at all. It is the same pressure that
put _farplanedist in Quake III: for maps open enough that PVS stops helping, cut
on distance instead. One engine bolted that onto its compiler. The other built its
sky out of it.
The sky as the far bound. Quake III ships _farplanedist, which cuts the
world at a radius, paired with _foghull, which fills the space the culled
geometry vacated so the player gets fog instead of hall-of-mirrors. Same problem,
same two halves: cut the world at a distance, and put something where it used to
be.
It is what each engine puts there that differs, and they are near opposites.
A foghull is six identical 8x8 pixel textures, each a flat fill of exactly the fog colour — 8x8 to save texture memory, because there is no detail in it to hold. It is declared in the worldspawn beside the far plane distance, which must be set beyond the fog’s distance-to-opaque, and if you set it too close the hall-of-mirrors comes back and you see the seam it was covering. Three hundred and eighty-four pixels, colour-matched so the player cannot tell it is there. Its success condition is being invisible.
Titanium’s is the sky. Nine hundred and sixty-two vertices, the most looked-at object in the frame, the subject of this entire post. There is no distance test anywhere; the dome is opaque, it is drawn first, and it writes depth at the fog radius, so the culling falls out of the depth buffer already containing the sky.
Quake III adds a deliberately invisible object to hide the fact that it culled. Titanium gets the culling free from the object it was always going to draw. The cosmetic object and the culling mechanism are the same object, which is the whole trick.
One object. The dome is opaque, drawn first, and writes depth at the fog radius, so past the bound the geometry IS submitted, transformed and rasterised, then discarded a fragment at a time by a depth value already in the buffer. What is left behind it is the sky, because the thing doing the culling is the scenery.
Seen from above, the difference is a count. Quake III draws two boundaries: a rule that removes geometry and paints nothing, and a hull that paints and removes nothing. Titanium draws one, and it does both jobs because it happens to be opaque and to go first.
Flip between the two panels and the view from the eye barely changes, and that is not a weakness of the figure. It is the requirement. A foghull is six flat fills matched to the fog colour precisely so that nobody can tell it is there; if you could spot the difference, the technique would have failed. Both designs are supposed to converge on the same picture, and they do.
What differs is the work, so that is what the figure counts. Past _farplanedist
the geometry is never submitted — the test threw it out before the draw call, and
those vertices cost nothing. Past the dome it is submitted, transformed,
rasterised, and only then discarded, a fragment at a time, by a depth value already
in the buffer. Same pixels. One of them paid for them.
There is a second difference, in what each engine gives up. Quake III’s sky is
drawn last, masked to the surfaces a mapper flagged as sky, at a depth range
pinned to 1.0, 1.0 — the far end. It cannot occlude anything; that is the point
of putting it there. It fills whatever the world did not cover, in two passes: the
outer box, then a cloud layer over it.
And _foghull is not an extra object beside that sky. It is the sky — the key
names a sky shader, and the map’s skybox becomes six flat fills of fog colour. So
the price is not one more object in the frame. The price is the sky itself: a map
that hides its cut this way gives up having a view.
That is the trade. Quake III keeps the far geometry cheap with a distance test, and where it needs the seam hidden it pays for that by flattening the sky. Titanium does the work instead — everything is submitted and rasterised and most of it thrown away — and gets its culling free from a sky it was going to draw anyway.
The zone buttons on that figure are worth pressing.
FOGSTART is not a free parameter. The client computes max(0, FOGEND - RANGE),
where RANGE is fog_far - fog_near — a pair the server sends on zone-in. So
the width of the fade is per zone, and it varies enormously:
| zone | fog runs from | to | share of the view |
|---|---|---|---|
| Greater Faydark | 16 | 306 | ~95% |
| Crushbone | 10 | 400 | ~98% |
| East Karana | 410 | 1200 | ~66% |
Greater Faydark is fog almost the entire way in — which is exactly how anyone who played it remembers the place. You cannot see the far side of the forest, and that is not the trees.
(The rendered frames on this page use East Karana’s own row from that table, so the haze in them is the zone’s rather than a fallback.)
What it adds up to
Nothing in this design is clever in the way a modern sky is clever. There is no scattering integral, no cubemap, no shader doing physics. There is a small image, a sphere oriented at the sun, a handful of billboards and grids, and an order.
What it does have is one source of truth. Ask any piece of that sky where its colour comes from and the answer is the same 32x32 file — the dome by row, the clouds by row and column, the horizon band by row 30, the fog and the ambient light and the sun and the moon by four texels in the last column, and the stars by an alpha channel that turns them off without a line of code being run.
Change the file and you change the weather, the lighting, the time of day, and whether there are stars. That is not a small thing to get out of a thousand pixels.
Two kinds of figure on this page, and the difference matters.
Anything that looks like the sky is the sky: a PNG rendered headless by the same renderer this post describes, at one pose out of the reference set it is byte-compared against. It is a still image rather than a live canvas because the alternative is shipping Daybreak’s art to you.
Everything else is a diagram — the dome’s rings, the ColorMap, the nested shells, the culling geometry, the cloud patch, the twinkle table. Those are drawn in your browser from the renderer’s own constants, and once again at build time into an SVG for readers without WebAssembly. No colour, radius, count or table entry in any of them was chosen: they come from the renderer, or from a measurement of the ~92 texels of the install it actually reads. The synthetic markers in the culling figure are the one exception, and they are labelled.
There is no second implementation of this sky anywhere in the page. A diagram that restates the renderer’s arithmetic is a diagram that can disagree with it, and a figure that disagrees teaches the wrong sky.