VIPER 11 · NotesFly it →

Note 1 of 15

Clouds you can fly through

How a browser draws a cumulus you can climb into, one step of a ray at a time.

A minute into the flight, the climb carries you straight into a cumulus cloud. For about two seconds the world turns white. Water beads on the canopy and streaks aft, the sound goes thick, and then you come out the other side into blue. Nothing about that moment is a video or a painted backdrop. The cloud is a real three-dimensional object. It is drawn fresh every frame, from wherever the jet happens to be.

Most games paint their clouds onto a sky dome, because a painted cloud costs almost nothing. The catch is that you can never reach it. A flight game that climbs to 16,000 feet has to fly past clouds, over them and through them, so VIPER 11 uses volumetric clouds instead. A volumetric cloud has no surface at all. It is a block of space filled with a density value: zero in clear air, higher where the cloud is thick.

A ray, one step at a time

To draw a pixel, the renderer sends a ray from the camera through that pixel into the sky and walks along it in steps. At each step it asks two questions. How much cloud is here? And how much sunlight reaches this point? It answers the second by taking a few more steps from that point toward the sun. If there is thick cloud between the point and the sun, the point sits in shadow. If there is not, it glows.

Figure 1 · Raymarching

A ray leaves the eye and samples the cloud at growing intervals. Wherever it finds cloud, a short march toward the sun (gold) checks how much light gets in. The bar below shows how much of the background is still visible through the cloud so far (the transmittance) and the colour the pixel has collected. In the game the march takes up to 500 steps. They start about 50 m long (18 m when you are flying near the cloud layer), grow 1% each step, stretch out across empty sky, and stop once 99% of the light is blocked.

Light fades exponentially as it travels through a cloud. This is the Beer–Lambert law: the fraction that gets through is e−σd, where d is the distance and σ is how strongly the cloud scatters light. Each step multiplies the running transmittance by that factor. The renderer adds up the light scattered toward the camera, and once almost nothing gets through any more, it stops early.

Figure 2 · Beer–Lambert

Transmittance against distance through cloud. At σ = 0.05 per metre, about what you find in a dense cumulus, you can see roughly 3.9 / σ ≈ 80 m. That is why the steps have to shrink near cloud: one 50 m step can skip straight past a wisp.

The silver lining

Cloud droplets mostly scatter light forward, so a ray of sunlight that hits a droplet keeps going in roughly the same direction. Look toward the sun through the edge of a cloud and you see that forward-scattered light as a bright rim: the silver lining. Look at the same cloud with the sun behind you and it is softer and flatter.

The game models this with two Henyey–Greenstein lobes, a standard textbook shape. One leans forward (g = 0.7) and one leans slightly back (g = −0.2), mixed half and half. Light that bounces around inside the cloud is approximated with eight progressively weaker “octaves” of scattering, and a “powder” term darkens the thin edges facing the sun, the way real cumulus looks bumpy and lit from within.

Figure 3 · Phase function

How much light a droplet sends in each direction, for sunlight arriving from the left. The dashed circle is a droplet that scatters evenly. Drag the angle: looking almost into the sun (0°), the cloud sends you several times more light than an even scatterer would.
The climb out of Nellis, 1,500–3,000 ft. The bases sit at 7,300 ft and the tops reach 10,800 ft. A thin stratocumulus layer floats at 11,800–12,600 ft and a cirrus sheet at 28,000 ft.

Doing it sixty times a second

Hundreds of samples per pixel, each with its own trip toward the sun, is far too much work for every pixel of every frame. So the renderer cheats in time. Each frame it marches only one pixel in every 4 × 4 block, and the next frame it marches a different one, in a fixed scattered order. After 16 frames every pixel has been visited. Each new frame is blended with the history of the frames before it, shifted to follow the camera’s motion, so the image stays sharp while the jet moves.

Figure 4 · Temporal upscaling

Sixteen frames, one sample per 4 × 4 block each frame, in the 4 × 4 Bayer order the cloud renderer uses (0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5). The newest samples are gold.

Shadows work the same way: they are computed once and then reused. A separate pass marches the clouds from the sun’s point of view into three cascaded shadow maps (512 × 512 each, out to 80 km), and both the ground and the clouds read from them. That is what darkens the desert under each cumulus. While the jet is still on the runway, the cloud shadows on the ground are switched off, because the afternoon sun would lay a cloud’s shadow right across the takeoff roll. They come on as you climb into the cloud layer, hidden by the whiteout. That one is an artistic choice, not physics.

When the frame rate drops, a governor lowers the cloud quality one tier at a time. The resolution scale falls from 1.0 to 0.5, the step budget from 500 to 280, and the shadow maps shrink.

Inside the cloud

Three seconds before entering the cloudHalf a second before the core of the cloud3 s outInside
Drag to compare. Left, three seconds before the climb enters the cell. Right, half a second before its core: the near-field march’s wisps, the whiteout closing in, droplets forming on the glass.

The cloud renderer was built to look at clouds from outside. With the camera inside one, it gives a flat blue haze. VIPER 11 adds its own near-field pass for the first 900 metres of every ray while you are in or near cloud. It takes 20 fine steps out to 450 m and 8 coarser ones beyond, at half resolution with blue-noise jitter. It also adds wisps of detail anchored to the world, so they rush past at the jet’s real speed.

Every other frame, a tiny probe reads the cloud density right at the camera. That one number drives everything else. The canopy gets wet at a rate set by how deep you are in the cloud, the droplets stretch into streaks as airspeed rises, and they evaporate over about 0.8 s once you are out. The sun on the cockpit dims to 30%. The world’s sound passes through a low-pass filter that slides down toward 700 Hz. The scripted climb is aimed through the flank of a single placed cell, 1.5 km across, so you spend about two seconds inside it.

Level at 16,000 feet over the cumulus carpet
Level at 16,000 ft. Clouds are sparse over the airfield and fill in to a full deck 9–22 km away. The layout comes from a seeded 2048 × 2048 weather texture that repeats about every 100 km, so every flight gets a different sky.

Credit where it is due

The far-field clouds and the sky are takram’s three-clouds and three-atmosphere, open-source (MIT) libraries for three.js by Shota Matsuda. They provide the layered cloud raymarcher, the temporal upscaling and cloud shadow maps, and a precomputed model of how sunlight scatters in the atmosphere (Bruneton’s), which gives the sky its colour and the far ground its haze. They in turn build on published work from Horizon Zero Dawn’s Nubis, Frostbite and others. VIPER 11 adds the weather layout, the placed cell, the inside-the-cloud pass, the droplets, the cockpit lighting and a few fixes needed to make it all hold up in a fast-moving cockpit.

Fly it

viperoneone.com

VIPER 11 is a browser flight game: three.js, WebGL2 and Web Audio. Every figure on this page is drawn from the game’s own code and numbers. Made by apek. Follow me @apekshik for what’s next.