HEIGHTMAP GAMES/ studio

FIELD NOTES — LIGHTING

Mountains that cast shadows

Until today a mountain in Orebound shaded nothing more than eighty meters from you. Now the land shades itself on every graphics tier, metal shows the sky, the forest reaches the coast, smoke leans with the wind and a storm has a voice. And the bug that made the new shadows blink, which one evening found in a minute.

Orebound has always had sun shadows, and they have always come from one place: a shadow map, a picture of the world taken from the sun's point of view, covering a box around the player. On the high graphics tier that box is a hundred and sixty meters across. Everything outside it is lit as if nothing stood between it and the sun. On the low tier there is no box at all.

That is fine for a belt, a smelter or a tree. It is not fine for a mountain. Our peaks rise well over a hundred meters, and in the evening a mountain should throw its shadow the better part of a kilometer across the land below it. It threw nothing. A valley at dusk was lit exactly like the ridge above it, and the relief the world generator works so hard on went flat at precisely the hour it should look its best.

This post is about fixing that, and about the rest of a day spent on light and air: metal that shows the sky, a forest that reaches the coast, smoke on the wind and a storm you can hear. It is also about a bug we shipped, and how an evening found it.

Two views of the island from 450 meters up in the late afternoon, looking away from the sun: the top one evenly lit, the bottom one with the valleys and their lakes in the shade of the mountains.

FIG. 1 — The island from 450 meters at 17:12, looking away from the sun. Top: before. Bottom: today. The lee valleys and their lakes fall into the mountains' shade, and the ridges stand up from the land again.

The land shades itself

The trick is an old one from terrain rendering, and it rests on one observation: for a given sun, a point on the map is lit exactly when it stands above a certain height. Call that height the shadow top. Out on a plain it is far below the ground. In the lee of a ridge it hangs in the air above you, sloping down away from the ridge at the angle of the sun.

Computing it is one sweep. Take the map's heights on a four-meter grid — 529 cells a side over the 2.1-kilometer island — and walk across it away from the sun, row by row. Each cell's shadow top is the larger of the shadow top and the ground of the cell behind it, the one nearer the sun, minus how far the sunlight drops over one step. One pass over 280,000 cells, no searching. It runs in a background worker, so the game never waits for it.

The result is a texture, and every lit surface in the game — ground, trees, machines, belts and water — looks up its shadow top once and compares it with its own height. So they all agree: a smelter in the shade of a hill is as dark as the grass around it. And the low tier, which has no shadow map, gets the mountains' shadows too. They are the first shadows of any kind the low tier has ever drawn.

The sun moves, so the sweep follows it. When the sun has turned about a seventh of a degree — once a second or so at normal speed — the worker sweeps again and the new shadow fades in over a second. The fade blends the shadow tops themselves, not the light, so the edge of a shadow slides across the valley instead of dissolving and reappearing. That, at least, was the design.

What it costs: on our desktop graphics card, looking down a valley on the high tier, a frame took 8.5 to 8.9 milliseconds with the mountains' shadows and 8.7 to 9.0 without. The difference is under the noise. On the low tier it was 1.8 to 2.0 both ways. The worker uploads one 1.1 MB texture a second at most.

The bug an evening found

We shipped it in the late afternoon. The same evening it came back with one line: the mountain shadows are switching on and off with a constant speed.

They were. We measured the brightness of a frame over a shadowed valley while the sun moved, sped up threefold to make it quick to see. It ran a sawtooth. Every second the shade vanished — the frame jumped up by nine to eleven levels out of 255 — and faded back in over the next second. Fourteen reversals in twelve seconds.

The cause is a convenience in the graphics library we build on. Every material keeps its own copy of its settings, made when its shader is first compiled, and for a texture setting that copy is a separate texture object. The copies share the same pixels in memory, but each one only sends those pixels to the graphics card when it is told that its own pixels changed. We only ever told the original. Counting in the running game, 909 compiled materials held 139 different copies of the shadow texture, and every one of them was stuck at its first upload. So each time the sun moved, the fade restarted from a stale picture with no shadow in it, and faded back to another stale one.

The fix is small: the shadow texture now refuses to be copied, so every material holds the one real texture — the same pattern our ground fog already used for its own shared texture. The sawtooth became a smooth slide, and the shadows lengthened with the evening as they should.

The same bug had a second face. With the shadows stuck, a lake that a ridge should hide from the sun still caught it, and the sun shone off the water from behind a mountain. Fixing the copies fixed that too: the sun's glint on water is lit by the same light and tested against the same shadow.

A lake at sunset with the sun hidden behind a forested ridge: on top, a bright sun streak across the water; below, the lake in the ridge's shade.

FIG. 2 — The sun at nine degrees, behind a ridge that rises to thirteen. Top: what the stale copies drew, a sun shining through a mountain. Bottom: the lake in the ridge's shadow, as of the fix. The glow above the ridge stays, as it should.

How did it pass our checks? We had looked at the new shadows carefully — from the air, from the valleys, at two times of day — with the sky clock paused, so the sun stood still and the first sweep was never replaced. Anything that follows the sun now gets checked with the sun moving.

Shadows that fade instead of stopping

The near shadow map has an edge, and it used to be a line on the ground: walk away from a wood and its shadows ended in a straight row eighty meters from you. Shadows now fade out over the outer fifth of the box instead. The box is also sized per graphics tier — eighty meters each way on high, sixty on medium — so medium's smaller shadow map covers a smaller area and its shadows came out noticeably sharper.

Metal that shows the sky

Most of a machine is metal, and metal is strange to light. It has almost no colour of its own; what it shows is a reflection of its surroundings. In Orebound a machine's surroundings were one soft light from the sky and one from the ground, so in shade — under a cliff, behind a building, at dusk — a fabricator was a black silhouette with orange windows.

Now metal reflects the sky. The game already renders a small picture of the sky, 128 by 64 pixels, for the water to mirror. That picture is now also blurred into a set of reflections, from mirror-sharp to soft, and handed to every material that is at least thirty percent metallic, and to glass. It is remade when the sky changes, at most twice a second. It adds reflections only; the soft fill light stays as it was.

A fabricator in the shade of a spruce wood: on top, a near-black silhouette with glowing windows; below, the same machine in readable copper, iron and brick.

FIG. 3 — A fabricator in the afternoon shade of a spruce wood. Top: before. Bottom: with the sky in its metal.

Our first attempt gave the reflections to everything, and the Depot took a cold blue-grey veil over brick and paint that had been tuned warm without it. The Depot's finishes sit below the thirty-percent line, so it looks exactly as it did; the machines, whose metals run from thirty-five to eighty percent, gain the most. The cost was within our measuring noise.

A forest to the coast

Trees in Orebound are drawn at four levels of detail, the furthest a flat card carrying a baked picture of the tree. Earlier today the rings between those levels moved out to 100, 250 and 500 meters, but trees were only drawn to 450. The cards were never used, and from any hilltop the forest stopped short and the land beyond it was bare until the haze took it.

Trees are now drawn to 1.5 kilometers on the high tier, 1.1 on medium and 750 meters on low, where the fog has already closed in. From the island's highest point the old build drew no cards at all; today it draws between 2,800 and 4,600 of them, two triangles each, and the forest reaches the sea.

The view from the island's top towards the coast: on top, the far hills bare; below, the same hills forested down to the shore.

FIG. 4 — From the island's top. Top: before, the forest ending a few hundred meters out. Bottom: today.

This one does cost something. Sorting the extra trees each frame takes about a millisecond more on the high tier in a development build, and roughly half that in a release build. Refreshing the far cards less often than every frame is the obvious next step.

Smoke on the one wind

Everything in Orebound that moves in the wind reads one wind: the trees, the clouds, the rain, the waves on the sea. Everything except chimney smoke, which drifted along a direction fixed in the code. Smoke now leans with the same wind as the rest, and drifts faster when it blows harder. We checked it by measuring a plume's heading against the server's wind: within a degree, where the old drift had been off by almost eighty.

A storm you can hear

The last post ended with an apology: no sound for rain or thunder, and the cascades did not roar. All three have a voice now. Rain plays at the strength of the weather, and when there is a roof over your head it turns into drumming on it. Thunder follows each flash after the time sound needs to reach you — the distance divided by 343 meters a second — from a direction and a distance the flash draws from the shared clock. So everyone on a server hears the same strike from the same side: a crack when it lands within 1.2 kilometers, a long rumble when it lands further out. And by the river chutes that already breathe mist, the water now roars.

These are synthesised stand-ins, like every sound in the game except the narrator. Real recordings are coming later.

Easier on the eyes

A storm flashes, and not everyone wants it to. The settings have a new Controls and comfort card: field of view, mouse speed, invert-Y, how much the view bobs as you walk, Reduce flashing — which turns each lightning flash into one soft pulse — and Reduced motion. Both comfort switches start on when your computer asks for reduced motion.

What it costs, and what is still rough

The mountains' shadows, the soft shadow edge and the sky in the metal are all inside our measuring noise on a desktop graphics card. The far forest costs about a millisecond of CPU in a development build. None of it has been measured on a laptop's integrated graphics yet, which is the machine the low tier is for, and that is the next number we owe.

Still rough: the sweep only knows the land, so buildings and trees cast shadows only as far as the near shadow map reaches. A mountain's shadow is as sharp three hundred meters from the ridge as at its foot, where a real one softens with distance. Water reflects only the sky, never the land, so beside a hidden sun a lake shows the sky's glow where real water would mirror the dark mountain. And the storm's voice is a placeholder until the recordings arrive.

← All field notes · Get the next one by mail