Haul Away the Frame

A sea shanty of how this page draws HMS Victory, sung over shots from its own renderer. Each verse is a part of it; the notes under the verses say what the lines mean and where the code is.

Sail with her live · Watch the film · Credits

Verse 1: the page

Oh, there's a ship called Victory that sails inside a tab, (Way, hey!)
No engine stowed below her decks, just shader code we had, (Way, hey!)
WebGPU to drive her on, each frame drawn fresh and new,
One wind, one sun, one little file, and all the rest pulls through.

The scene is a web page on WebGPU with shaders written in WGSL and no game engine under it. Nothing is baked into video: every frame is drawn as you watch. One wind vector and one sun in src/config.js drive the waves, the whitecaps, the sail trim, the heel, the flags, the light and the shadows.

Verse 2: the waves

The waves come from a spectrum, boys, the kind the North Sea gave, (Way, hey!)
Three fast Fourier cascades make the chop on every wave, (Way, hey!)
And four-and-sixty longer swells are summed by sine, it's true,
So the hull can feel on the CPU the very sea you view.

The sea state is a JONSWAP spectrum (the Joint North Sea Wave Project's). Waves shorter than 25 m come from three FFT cascades tiling 137 m, 32 m and 7.5 m. The 64 longer bands (48 wind waves, 16 swell) are summed analytically, so JavaScript can evaluate exactly the same sea for the ship's motion as the GPU draws (src/spectrum.js, src/ocean.js).

Chorus

Heave away, haul away, pixels on the tide!
Sixty frames a second with the wind upon our side!
Heave away, haul away, shaders in a row!
Five milliseconds to draw her, boys, and off again we go!

The opening view costs 5.1 ms a frame at 2560 x 1440 on an M-series Mac.

Verse 3: the sea's mesh and its glitter

The sea's a single grid of points we cast from the camera's eye, (Way, hey!)
It bends around the curving earth until it meets the sky, (Way, hey!)
Six-sided tiles with shuffled starts hide where the waves repeat,
And we keep each slope and its variance, lads, to make the glitter sweet.

The ocean is one projected grid: a lattice even on the screen, cast onto the sea, its rays met with the curved earth so the sea's edge is the true horizon. Each FFT cascade is sampled through hexagonal tiles with random offsets, which hides its repeat. The slope textures also carry the slopes' squares, so every pixel knows the variance of the waves it cannot resolve (LEAN mapping) and far-off water still glitters correctly.

Verse 4: white water

Where the water folds upon itself, the Jacobian runs low, (Way, hey!)
And there the white horses are born, and the foam map lets them grow, (Way, hey!)
A Kelvin wake baked for her hull spreads out astern in a vee,
And the bow wave rides a mesh of its own, so it holds its shape at sea.

A wave breaks where the surface's horizontal displacement folds, which shows as the Jacobian falling below a threshold (calibrated to Monahan's whitecap cover for the wind). A foam record that follows the ship grows, spreads and fades the whitecaps. Her wake is a thin-ship Kelvin pattern baked to a texture, and the bow wave has a fine mesh fixed to the ship, so it does not change shape as the camera zooms (src/wake.js, PATCH in src/ocean.js).

Verse 5: the ship's level of detail

She's built in code from her builder's lines, a million triangles in all, (Way, hey!)
In clusters of six score and eight, like Nanite, great and small, (Way, hey!)
Twelve levels deep, each one cut by half, in a graph we stow below,
And a compute pass picks the coarsest cut that a pixel wouldn't know.

The ship is generated by code from her real dimensions: 983 thousand triangles. They are cut into clusters of up to 128; groups of clusters are simplified to half and re-cut, twelve levels up, into a graph (Nanite's scheme, built with meshoptimizer). Each frame a compute shader picks, per ship, the coarsest clusters whose error is under a pixel (src/dag.js, src/shipRender.js). The film shows the clusters in colour.

Verse 6: sails, shadows and sea-keeping

Her sails are patches bent by wind, the CPU sets each one, (Way, hey!)
The flax lets through a twentieth part of the light of the afternoon sun, (Way, hey!)
Each ship keeps a shadow map of her own, her ropes are ribbons fine,
And three-and-twenty strips of hull feel the heave and the roll of the brine.

Sails are Coons patches between their yards and clews, shaped on the CPU each frame; the canvas transmits 5 to 9% of the light behind it. Every ship has her own sun map fitted to her, never to the camera. Rigging is drawn as camera-facing ribbons that keep a rope's coverage when it is thinner than a pixel. Her heave, pitch and roll come from strip theory at 23 stations along the hull (src/sails.js, src/rigging.js, src/physics.js).

Verse 7: sky and clouds

The sky comes out of Hillaire's tables, sunlight scattered through, (Way, hey!)
The clouds are marched by rays through noise, and a quarter size will do, (Way, hey!)
We gather them over many frames till the speckle's washed away,
And the sea reflects that selfsame sky, so the two agree all day.

The atmosphere is Hillaire's 2020 model: lookup tables for transmittance, multiple scattering and the sky view. The clouds are ray-marched through noise at a quarter of the resolution each way and accumulated over frames in their own history. The sea reflects a map of that same sky, so water and sky always match (src/atmosphere.js, src/clouds.js).

Verse 8: the lens

We draw her small and build her sharp from the frames that went before, (Way, hey!)
A shutter half open blurs the spray, and the highlights bloom and pour, (Way, hey!)
Then AgX rolls the brightness off, the way that film would do,
And the resolution trims itself to hold the frame rate true.

Temporal anti-aliasing draws each frame below the window's resolution and builds the full picture from earlier frames. Motion blur is a 180-degree shutter; bright light blooms; AgX is the tone curve. The render resolution steps up and down while you watch to keep every frame on the display's refresh (src/post.js, src/pace.js).

Outro: the fleet

Ten more ships astern of her, one mesh between them all,
And the whole fleet sails in a browser tab. Haul away, haul!

The ten other ships share the flagship's model, cluster graph and pipelines; each has her own motion, sails and shadows (src/fleet.js).

Sail with her live · Watch the film · Credits