Making of "Hello World"
The reference demo taken apart

Table of Contents

↑ Back to the index

The reference demo (demos/hello-world/main.c and demos/hello-world/shaders/) is an intro of about 279 seconds in three scenes and an end card. It plays once and stops. It also serves as a tour of the engine. This page takes it apart. It starts with what the show is and when each thing happens. Then it goes through how each scene and the end card are drawn, the music and how to change it, the notes that apply across the scenes, and how the program ends.

The linked video and the repository stills show the demo as it is now. They were rendered offline with Mesa on Intel UHD 620 integrated graphics, at 3840x2160 internally and downsampled to 1920x1080 for 60 fps playback. The 16,722-frame master took about 57 minutes to render before encoding.

Some performance measurements and image comparisons on this page were made on an earlier version. It still had cumulus clouds and dust motes in the jungle and beach reflections of nearby objects, and its seabed placement, predator path, camera work, materials, animation, music and end-card wording were different. Unless a figure is identified as current, it hasn't been measured again since.

1. The concept

The demo plays three scenes and an end card back to back, then stops:

  • Scene 1, the big bang (50.3 s). A single bright dot hangs in a dark void with faint stars. On the first kick drop it bursts outward: the exposure kicks, a shock front races through the sky and over the floor, lighting up the dark universe behind it, and the camera jolts. The burst leaves about 180 procedural shapes in pastel plastic, iridescent film and tinted glass, which orbit, spin and pulse to the beat over a glowing synthwave grid. At the second hook's downbeat the surviving shapes resolve into a five-turn spiral facing the camera, then loosen back into their orbits. Near the end the crowd recedes and the title MICROCOSM assembles itself out of flying cubes, under the blob. A synthwave sun rises behind the title and a light sweeps across it. Then everything collapses back to a dot as the camera pulls away, until the seed is a lone star in the black, up to the right.
  • Scene 2, the descent and the deep (104.5 s). A sun rises where the star went out, and a blue-and-green planet appears in space beside it. The camera falls toward the planet, through the cloud deck and the sky and into the sea, and sinks into the deep, where the blob from Scene 1 is waiting without a face. The blob sets off on a wandering swim across the seabed and the camera follows. As the murk clears, the view fills with sand, boulders, swaying weed and circling schools of fish, all placed so that nothing intersects. A blue whale comes out of the clearing water and passes overhead with her calf. The camera eases back to give them room as the music opens out, and a realism push makes everything denser and more detailed. Moon jellies pulse with cyan light as the water darkens briefly. Near the end a big, dark predator comes out of the murk and stalks straight at the blob, which hasn't noticed. It lunges with its jaws wide open and only just misses. Only then does the blob panic and bolt, and the predator turns to chase it. Once it has got away, the blob changes. The camera circles it while its eyes grow from nothing to their Scene 1 size (it has none until the encounter is over), and its lumpy shape rounds out into a sphere. Then, still in a panic, it bolts on across the ocean, further and faster. It spirals outward into fresh water and uncovers more and more complex structures: reef formations, kelp forests, coral gardens, crabs and schools of fish. One is a big school that it bolts straight through, the fish parting round it. By the end it is racing through a complete reef full of life, its coral massed on a bed of its own growth, and the picture burns out to white.
  • Scene 3, the jungle (114.2 s). The white clears to the floor of a rainforest. Giant buttressed trees are hung with lianas, and there are palms and saplings, ferns, broad leaves and red heliconias over a litter of fallen leaves, with shafts of sun slanting through the humid haze. The camera looks round the clearing and climbs the trunks to the canopy as scarlet macaws cross a gap in the leaves. Then it comes down to a broad leaf beside us, where a seven-spot ladybug is walking. We go in close, and the ladybug opens its wing cases and flies up into the light. The camera lets it go and glides off between the trunks, stopping well back among them where two trunks frame a wide view of a beach. Tiny glimpses of a tyrannosaurus appear between the trunks before its head and shoulders emerge, about 21 m away. As it comes, the camera crouches low in the ferns on a longer lens, and its footfalls jolt the picture over a low, dissonant pulse. It walks out of the crowns' shade into a pool of sun, stops to look almost straight at us and opens its jaws with two snorts. We recoil, then hold still as it turns away and disappears behind a trunk. The dark music and quiet forest hold until it has gone. Then the camera steps out to the last row of trees. The blob from Scenes 1 and 2 rises out of the surf, now with two tiny new arms. As it hops up the beach the camera walks on out of the trees to meet it, and the lens lengthens until the blob stands about a fifth of the frame high. It settles on the wet sand, looks round and waves at us. Then it turns to the sea and plays in the swash. It hops down after a draining wave, jumps over the foaming edge of the next and lands in it in a burst of spray. The spray throws up a straw sun hat, a striped beach parasol and a folded deckchair. The blob catches them: the hat lands on its head and the parasol springs open over it. It stamps and splashes, and comes out of the water with them for the last time. It shivers itself dry on the sand and sits under its parasol watching the sea as a last wave laps round it. Meanwhile the camera goes back up among the trees and widens for a last wide view, and the scene fades to black on it.
  • The end card (9.7 s). After the black we're back in Scene 1's world, with its violet sky, glowing grid and cubes. "HELLO MICROCOSMIC WORLD" flies together on three lines to a reprise of Scene 1's tune. The blob drops in from above, looking as it did on the beach: glossy blue jelly with big eyes and its little arms. It bounces along the words like the ball in a karaoke video, one syllable a beat, and each syllable lights up as it lands. Then it somersaults up onto HELLO, puffs itself up, flings its arms up and winks at us. The sun behind the words sets, the show fades out and the program exits.

Each scene plays once, with its own look and its own music, and hands over to the next without a visible cut. Scene 1 goes black round a last star, and Scene 2's sun rises in its place. Scene 2 burns out to white, and Scene 3 opens out of the white. Scene 3 fades to black, and the end card comes up out of the black.

Everything is generated at runtime, and there are no asset files. The meshes are procedural and drawn instanced, an HDR post stack finishes the picture, the camera follows a spline and a software synth plays the soundtrack.

The main constants at the top of demos/hello-world/main.c:

Constant Value Meaning
SCENE1_DUR (DUR) 50.3 s Scene 1's length: 26 bars, the song's first movement
SCENE2_DUR 104.5 s Scene 2's length: the second and third movements, 54 bars
SCENE3_DUR 114.2 s Scene 3's length: the fourth movement, 59 bars
SCENE4_DUR 9.7 s the end card, 5 bars
TOTAL_DUR 278.7 s the whole show, 144 bars (4:39), after which the release build exits
BPM 124 locked to the soundtrack
T_BANG 7.7 s the bang, on the first kick drop
T_LOGO_IN 32.7 s the logo starts to assemble (hook B)
T_LOGO_OUT 43.0 s the logo disperses
T_COLLAPSE 44.6 s outro: everything returns to a dot
T_BLACK 49.2 s the world is black round the seed, now a star up to the right
NBODY 182 orbiting shapes
MAX_LOGO 512 cube budget: the logo's 142 and the end card's 323
WIN_W x WIN_H 1280x720 the dev build's window (the release fills the screen, 16:9)
RENDER_W x RENDER_H 960x540 the headless screenshots' resolution

2. Timeline

The times below are show time, in seconds from the start. One clock drives the picture, the audio's playback position (mc_audio_time(), see Sound from nothing), and every scene reads it directly. Scene 1 follows the sections of the song's first movement (4/4/4/4/7/3 bars at 124 BPM, from src/synth.c). Scene 2's underwater act and Scene 3 are choreographed on story clocks of their own, stretched against show time. Their own sections give the beats in full, on those clocks: Scene 2 and Scene 3.

Part Show time On screen
Scene 1: intro 0-7.7 s A glowing seed, a bright ball with a tight halo, alone in a dark void with faint twinkling stars. The camera hovers very close.
Build 7.7-15.5 s The bang. On the first kick the seed bursts and the exposure kicks. A shock front races out from it through the sky, bending the sky like a lens and lighting up the dark universe behind it. It also runs over the floor and lights the grid out to the horizon, seconds before the grid fades in. The camera's aim jolts. Bodies pop in with an elastic overshoot and fly out to their orbits, the synthwave grid fades up, and the camera pulls out as the universe expands.
Hook A 15.5-23.2 s Full groove. About 180 shapes orbit and spin, and the emissive ones flash on the beat and feed the bloom. The camera flies the looping spline.
Breakdown 23.2-31.0 s A breather. Motion and brightness dip (the energy eases to ~0.5) with the music's filtered breakdown. Over its last bar, from 29.0 s, the surviving shapes gather into a five-turn spiral facing the camera.
Hook B 31.0-44.5 s A bigger reprise, with the energy back. The spiral resolves on the downbeat, holds for one beat and smoothly releases by 32.7 s. From ~33 s the crowd recedes and MICROCOSM assembles from flying cubes. The camera eases to a head-on view of the logo plane, aimed 1 m above it, with the blob floating in the sky over the title. A striped synthwave sun rises behind them, mirrored in the floor. On the downbeat of bar 19 (36.8 s), with the logo complete, a light sweeps across it in one beat, left to right on a slant, and the cubes it passes flare and hop. At ~43 s the logo disperses, the sun sets and the camera lets go.
Outro 44.5-50.3 s The collapse. Bodies shrink away, the grid fades out and the seed's glow swells again. Everything collapses to a dot as the camera pulls away, until the seed is a star up to the right, where Scene 2's sun will stand. Meanwhile the world goes black round it (by T_BLACK, 49.2 s). The star outlasts the world by a beat and is fading at the cut, and Scene 2's sun rises in its place.
Scene 2: space 50.3-60.0 s The sun comes up out of the black, then the brightest stars, then a blue and green planet with its moon. The camera drifts slowly closer.
Dive 60.0-64.9 s The camera accelerates straight down at the planet, which swells to fill the frame. Space turns to blue sky only at the very end.
Cloud break 64.9 s The picture flashes white as the camera punches through the cloud deck.
Sea 64.9-69.7 s The fall slows and levels out over a wavy ocean, until the lens rides the waves a hand's breadth above the water.
Breach 69.5-69.8 s The lens goes under without a cut, as a waterline sweeps up it.
Deep 69.7-117.0 s The blob, with no face yet, wanders across the seabed and the camera trails it. The murk clears, the realism push builds, and boulders, weed and schools of fish fill in. A blue whale and her calf pass overhead; at 91.8 s the camera has eased back and looks up at them as the music opens out. Later, moon jellies pulse with cyan light above the blob's way in darker water.
Encounter 117.0-129.1 s A predator comes out of the murk and stalks the blob, which hasn't noticed. It lunges at 121.6 s and misses by a hair at 122.9 s. Only then does the blob panic and bolt, and the predator chases it until it gives up (125.9 s).
Transformation 129.1-139.7 s The camera circles the drifting blob while its eyes grow in and its lumpy shape rounds out into a sphere.
Flight 139.7-152.3 s The blob bolts across the ocean, faster all the time, through reef formations, kelp forests and coral gardens. At 146.0 s it bolts through a big school of fish, which parts round it.
Burn-out 152.3-154.8 s The picture burns out to white.
Scene 3: white 154.8-156.8 s Pure white for a bar.
Look round 156.8-172.3 s The white lifts over 2.4 s onto the floor of a rainforest, and the gaze drifts across the clearing. Morphos flutter past.
Canopy 172.3-181.9 s The gaze climbs the trunks to the canopy, where scarlet macaws cross a gap in the leaves.
Descent 181.9-187.8 s The gaze comes down and round to a clump of big leaves beside us, and the camera closes in.
Ladybug 187.8-203.2 s A seven-spot ladybug walks along the hero leaf, a few centimetres from the lens.
Take-off 203.2-207.1 s It opens its wing cases and flies up into the light.
Glide 207.1-214.8 s The camera lets it go and glides off between the trunks.
Glimpse 214.8-216.8 s The camera stops among the trees, where two trunks frame a wide view of a beach.
Tyrannosaurus 216.8-224.5 s A tyrannosaurus gradually emerges between the trunks at the forest's edge, about 21 m away. Its footfalls jolt the picture while we crouch in the ferns, and the dark pulse tightens. It stops, looks almost straight at us and opens its jaws with two snorts.
Walking on 224.5-236.1 s It walks on out of the picture, and the forest stays silent until it has gone (232.3 s). Then the camera steps out toward the last row of trees.
The blob 236.1-245.8 s The blob rises out of the surf and hops up the beach, and the camera walks out of the trees to meet it as the lens lengthens.
The wave 245.8-249.7 s It settles on the wet sand and waves at us.
The play 249.7-261.3 s It plays in the swash. It jumps a wave's foaming edge, and the spray where it lands (257.7 s) throws up a sun hat, a parasol and a deckchair, which it catches.
Watching 261.3-267.0 s It comes out of the water for the last time, shivers itself dry and sits under its parasol to watch the sea. The camera goes back up among the trees for a last wide view.
Fade 267.0-269.0 s The scene fades to black.
End card 269.0-278.7 s "HELLO MICROCOSMIC WORLD" flies together in Scene 1's world, and the blob bounces along it one syllable a beat, then winks from the top of HELLO. The card fades to black over its last three beats, from 277.3 s.

The master fade (post.fade) brings the image up over the first 2.5 s. It isn't what takes Scene 1's world to black at the end. That's uAtmo in sky.frag (see The sky). The fade holds until a beat after T_BLACK and then dims the star, down to about 25% by the cut at DUR.

3. Scene 1: the big bang

Scene 1 is the show's first 26 bars, 0-50.3 s. A seed of light hangs alone in the dark and bursts on the first kick into a crowd of 182 shapes over a glowing synthwave grid. Later the crowd makes way for the title MICROCOSM, built from flying cubes, and at the end everything collapses back into the seed, which the camera leaves behind as a lone star. The concept tells the story, and the timeline lines it up with the music. This section is about how it's drawn. The end card, the show's last five bars (269.0-278.7 s), comes back to the same world to say hello. It has a section of its own after Scene 3's, The end card.

The C side is all in demos/hello-world/main.c. animate places the shapes, sim_step runs their collisions, camera_at flies the camera and s1_draw draws the world. Both render_scene1 and the end card's render_end draw through s1_draw. Five shader programs, all in demos/hello-world/shaders/, do the drawing:

Program Shaders Draws
obj object.vert, object.frag the shapes, the seed, the logo's cubes, the blob's eyes
shadowsh object.vert, shadow.frag the solid shapes into the key light's depth map
sky far.vert, sky.frag the backdrop: gradient, glow, stars, sun and shock front
grnd ground.vert, ground.frag the grid floor
blobsh blob.vert, blob.frag the blob

3.1. How a frame is drawn

render_scene1 works out the look of the moment, and s1_draw draws it in a fixed order:

  1. animate fills one instance list per shape type: the seed, the crowd, the title's cubes and the blob's eyes. On the end card, render_end fills them with its own cubes and eyes instead.
  2. The key light's depth pass draws the shapes, seen from the light, into Scene 2's sun map. The solids go through shadowsh and the glass through object.frag, which casts a third of a shadow (The key light's shadow).
  3. The Frame block goes up with the camera's view and the moment's colours, and mc_renderer_begin binds the HDR target.
  4. The sky, a full-screen triangle with the depth test and depth writes off, so everything draws over it.
  5. The grid, added on top. It's depth-tested but writes no depth, so bodies hide it and it can't hide them.
  6. The solid shapes, one instanced draw call per shape type.
  7. The blob, blended by a constant alpha. Like the grid, it's depth-tested without writing depth, so nearer shapes still hide it.
  8. If there's any glass in the lists, a copy of the picture so far, and then the glass, which looks through the copy.
  9. mc_renderer_end runs the engine's post stack with the moment's exposure, bloom and fade (Exposure, bloom and fades).

3.2. The acts and the envelopes

Scene 1 is split into four acts that follow the music's sections. Each has its own sky (SEQ_TOP above the horizon, SEQ_BOT below it) and its own pace (SEQ_SPEED):

Act Music Show time Sky Pace
I dawn intro, build 0-15.5 s a dusky rose 0.9
II play hook A 15.5-23.2 s deep blue 1.15
III deep breakdown 23.2-31.0 s calm teal 0.7
IV finale hook B, outro 31.0-50.3 s deep violet 1.1

The orbiting motion runs on motion_time(t), a warped clock that adds up real seconds, each scaled by its act's pace. It only ever climbs, and at a boundary only its rate changes, so nothing jumps. atmosphere() cross-fades the sky into the next act's over the 2.5 s before each boundary, so the change of colour lands on it.

The first two acts have deep, dark skies, so the pastel crowd stands out against them. The bottom colour is also the fog's, so the far side of the crowd sinks into a darker distance. The ambient light on the bodies' shaded sides comes from the same colours, so it stays low and they model well. With the tight glow of the hooks (The sky), the middle of the crowd is a warm hotspot against a deep sky. s1_draw brightens the colours with the energy (below): the top by 0.30 + 0.22 × energy and the bottom by 0.7 + 0.5 × energy.

The motion is kept smooth. Each body breathes by 5% on a sine of its own. The beat is a continuous sine (0.5+0.5*sin(beats*TAU)) that only makes the emission and the bloom shimmer, with no pop on each beat and no pump of the field of view. A smooth energy() envelope follows the sections. It rises over the 5 s after the drop, dips to half through the breakdown, comes back for hook B and dies away through the outro. Much of the brightness and motion is scaled by it.

expansion() is how big the universe is, from 0 (the dot) to 1. It creeps up to 0.02 before the bang, bursts open with an elastic overshoot over the 2.2 s from the kick, and holds. Between T_COLLAPSE (44.6 s) and T_BLACK (49.2 s) it eases back to 0. Every orbit's radius is scaled by it, and so is the camera's path. body_scale() sizes the bodies on the same cues. They spring in over 1.2 s with a little overshoot and shrink away through the collapse.

3.3. Procedural meshes and instancing

object.vert builds six shape types from gl_VertexID: a ball, a rounded box, a torus, a cone, a cylinder and a spiky sea urchin. Their meshes hold no vertex data, only a vertex count (mc_mesh_procedural, see Building geometry out of arithmetic). A per-draw uniform, uK, picks the shape: the shape type plus one, or 7 for Scene 1's balls. For the programs that share object.vert, uK 0 is Scene 2's seabed, built from the heights that uwplace.comp worked out.

  • The ball and the rounded box come from cube(), which builds the six n x n grids of a cube. Scene 1's ball uses 12 x 12 (uK 7: a 48-sided outline and 5,184 vertices), and the rounded box and the urchin's core use 8 x 8 (2,304). Each grid coordinate is bent by the odd polynomial u·(e + (1 - e)·u²). With e = 0.75 the cells come out even once they're pushed onto a ball. With e = 1.5 they crowd toward the edges of the rounded box, which is a 0.6 core grown by a 0.2 radius. The polynomial keeps 0, 1 and -1 exact and the grid coordinate is (2i - n)/n, so neighbouring faces meet bit for bit. A pow() warp would give NaN at 0 on the GPU, and cracks.
  • The torus (28 x 14), the cone and the cylinder (28 steps round, in base, side and top bands) and the urchin's 18 spikes come from lathe(), which turns a profile about +Y. The spikes have 16 steps round, which with the core brings the urchin to 7,488 vertices. Each spike points along its own Fibonacci (golden-angle) direction, so the spikes spread evenly over the ball. Each is also sunk 0.1 into the ball (centred 0.7 out, with a half-height of 0.3 and the tip at 1.0). So the depth test cuts the junction in a curve, and no lip stands out round the spike's base.
  • In a plain triangle list, a quad's corners are corner(v) = (22, 52) >> v % 6 & 1.

The box stops at 8 x 8 because at 12 x 12 its bevels cost 5 ms more on the UHD 620 with the earlier end card's 350 letter cubes, for no visible difference. The ball's mesh itself stays at 6 x 6 (uK 1, 1,296 vertices), because Scene 2 draws its bubbles, its specks of marine snow and the blob's eyes from it. At 12 x 12 those would come to about 2.9 M vertices a frame instead of 0.7 M, for specks a few pixels across. So s1_shapes draws Scene 1's balls (the crowd's, the seed and the blob's eyes) with four times the mesh's vertex count and uK 7. The mesh carries no vertex data, so asking it for more vertices needs no new buffer. animate skips the bodies while they have no size, before the bang and after the collapse, since a shape of zero size would still cost the depth and lit passes all its vertices.

ground.vert makes the 140 m floor the same way, from six vertices. Each frame the animation fills one per-instance array per shape type (mc_instance: a model matrix and a colour whose fourth channel is a code, see The instance colour's codes). s1_shapes draws each array with one instanced call (GPU meshes & instancing). It runs four times a frame (see How a frame is drawn), and only the first pass uploads the arrays, so each goes to the GPU once a frame. The whole crowd, and later the title's 142 cubes or the end card's 323, takes six draw calls a pass. The seed, the cubes and the blob's eyes are more instances of the ball and the rounded box, so they need no geometry of their own.

Each body has a fixed random character: its shape, orbit radius (2-8.5 m), spin axis and speed, colour, glow and base scale. build_bodies draws it from a fixed seed (mc_rng_seed), so the universe is the same on every run. An orbit is a base direction turned about Y over time, plus a vertical sway, and expansion() scales its radius from about 0 (the dot) up to full and back.

During the breakdown's last bar, from 29.03 s (T_FORM_IN), animate blends the drawn positions and scales into a five-turn spiral, 14 m in front of the moving camera. It resolves on hook B's downbeat at 30.97 s, holds one beat through 31.45 s (T_FORM_OUT), then releases by T_LOGO_IN at 32.67 s. The 159 surviving shapes in the reference release fit within the frame and remain separated at the peak. Original body indices assign the slots, leaving holes where bodies were absorbed instead of jumping survivors into new slots. Their types, colours and spin remain, and the collision simulation keeps running on the live orbits beneath this rendering transform.

3.4. Collisions (sim_step)

The base motion stays analytic, but a light fixed-timestep simulation (SIM_DT = 1/120 s, advanced by sim_advance) keeps a small physics offset per body (poff and voff). A damped spring pulls the offset back to zero, so a body that gets shoved flies off and then eases back to its orbit. From 2 s after the bang until the collapse, the sim also watches for real intersections. When two bodies overlap, meaning their centres are closer than the sum of their radii, it rolls once:

  • 25% of the time they combine. One body becomes a new random shape and colour, with a 40% pop in scale and a flash of glow, and the other vanishes.
  • 25% of the time they repel. They get equal and opposite impulses and fly apart, and the spring eases them back toward their orbits.
  • The other 50% they pass through each other.

Both bodies then wait 1.6 s before they can roll again, so a contact doesn't roll every frame. Combines stop once only 105 bodies are left (MIN_ALIVE), so the scene stays full. It's all deterministic, with a fixed timestep and one seed set by reseed_loop at startup, so Scene 1 always plays out the same collisions. That's also what lets the dev build's scrubber rewind it: going back, sim_seek reseeds the sim and replays it up to the new time (see the engineering notes).

3.5. Lighting (object.frag)

The lighting model is small and analytic, and its output is linear HDR, which the post stack tonemaps later. It has these terms:

  • Hemisphere ambient: mix(groundColor, skyColor, 0.5 + 0.5*N.y), from the act's sky colours.
  • Light from the glow. The glow round the origin, where the bang happens, lights the faces turned toward it. It falls off as cos θ / (2 + r²), with r the distance from the origin, at the sky glow's intensity (uGlow × uGlowAmt × 8). At the bang it lights up the inner faces as the shapes are thrown out. Through the hooks it's a faint warm light on the shapes near the centre, and it swells again with the collapse's last flare. The seed's faces all point away from it, so the seed is untouched.
  • Key diffuse: a warm directional light 39° up, whose direction circles once every 31 s. It casts shadows (The key light's shadow).
  • Blinn-Phong specular with a clear coat: pow(max(dot(N,H),0), 56) * 0.5 gives a broad sheen and pow(..., 600) * 3 a small hot glint inside it, both shadowed. The pastel plastic looks glossy, like candy or a lacquered toy, and not like matte clay. The TAA and the bright pass's Karis weights keep the glints on small shapes from sparkling.
  • Rim: pow(1 - max(dot(N,V),0), 3), tinted by the sky colour.
  • Emission, which pushes bright bodies past the bloom threshold on the beat (The instance colour's codes).
  • Exponential fog toward the sky's bottom colour.

Scene 1 runs no ambient occlusion and no depth of field. The shapes rarely come within the occlusion's one-metre reach of each other, and the sky and the grid write no depth to focus by.

3.5.1. The instance colour's codes

The instance's fourth colour channel is a code as well as an amount, and object.frag reads it that way:

  • From 0 up it's emission, at 4 × base². The logo's cubes, the end card's syllables and the seed use this.
  • Below 0, down to -1.6, it's the crowd's softer glow. build_bodies gives a third of the crowd an emission between -0.8 and -0.3, which animate scales with the beat and the energy. A combine's flash takes it further, down to -1.6, and makes a plain body glow the same way for a moment. object.frag turns it into 3 × base² × -emission × (0.3 + 0.7 cos θ).
  • Anything under -3 is a bubble of air under water. Scene 2's bubbles use -4, well clear of the crowd's codes (see What draws it).

The base colour is squared so that a glowing pastel keeps its hue through the tonemap. Squared, the palette's pink (1, 0.42, 0.62) glows as (1, 0.18, 0.38). So it stays pink instead of burning to a white tile with a tint, and the bloom carries the colour. The crowd's glow is full in the middle of a face and a third of that toward the silhouette. That way the key light, the clear coat and the rim still model the glowing bodies: they glow and bloom on the beat and are shaded plastic in between.

3.5.2. Three materials

The crowd has three materials. A quarter of the bodies are tinted glass, a quarter are iridescent and the rest are pastel plastic. build_bodies makes every other odd-numbered body glass and the rest of the odd ones iridescent. The even ones, and whatever a combine makes, are plastic, and so are the logo's and the end card's cubes. The material is carried in the signs of the colour, set once at load: a negative red means glass and a negative green means iridescent. That way animate, the sim and object.vert don't need to know, and object.frag takes the colour's absolute value. Everything else it draws (Scene 2's eyes and marine snow) is plastic, except Scene 2's bubbles, which are air (above).

  • The iridescence is a thin film over the plastic, about 0.4 µm thick with index 1.33, like a soap film or a beetle's shell. Light reflected off its two faces meets again out of step by 4π n d cos θ_t / λ. So its reflectance, 0.5 - 0.5 cos of that, comes and goes with the wavelength (650, 550 and 450 nm for the three channels) and with the angle. Face on it's magenta, going through gold and green to cyan at the edge. It's lifted to 0.3-1 and mixed 60% over the body's pastel, so it stays in the palette.
  • The glass is drawn last, after the blob, through a copy of the picture so far, the way Scene 2's jelly is. The copy is Scene 2's jelly_copy, the whole frame in RGBA16F on unit 9. The glass reads it along the ray bent into the surface (index 1.33), tinted by its pastel more the deeper it looks in (Beer-Lambert, exp(-2 (1 - base) cos θ)). It keeps 15% of the plastic's light, as if frosted, and mirrors the sky toward its rim by (1 - cos θ)³, which is Fresnel's shape. The clear coat's glint and the beat's emission go on top.
  • It reads the copy 0.7 m × cos θ / (1 + k) along the ray, where k is the surface's curvature: one over its radius, from the derivatives of the normal and the position. The cos θ makes the edge, which turns the ray fastest, bend it least. Dividing by 1 + k keeps the picture behind from being magnified past a fold, such as a cone's tip or a box's bevel, which would focus the copy's pixels into stair-steps. The distance stays at 0.7 m because k comes from derivatives, so it's one value per 2x2 pixel block. The point read jumps from block to block, and the jump grows with the distance. At 0.7 m the steps stay at a pixel or two, and the torus and the ball still clearly bend the grid behind them.
  • s1_shapes sorts each shape type's glass to the end of its instance list and draws instance ranges (mc_mesh_draw_instance_range): the solids in the lit pass, and the glass after the blob and the copy. When there's no glass, as on the end card, no copy is made. On the UHD 620 at 2160 lines the copy, which is bound by bandwidth, costs about 5 ms a frame, and the glass's shading about 2 ms.

3.5.3. The key light's shadow

The key light casts shadows, so the bodies shade each other, and the end card's letters do too. Each frame s1_draw first draws the shapes again, from the light, into Scene 2's sun map, uw_shadow. Scene 1 and the end card never share a frame with Scene 2, so the map is free while they run (see The renderer evolves). The map is 2048 x 2048 texels over an orthographic 32 m box round the origin, which makes 1.56 cm texels. That's big enough for the logo's cubes as they fly in from up to 16 m out. object.frag reads it through keyShadow(), which takes nine bilinear depth-compared taps 2 texels apart. Each tap's 2-texel filter meets the next, so the penumbra, about 9 cm wide, ramps smoothly. 4x4 taps look the same and cost 1.8 ms more on the UHD 620. The taps start from the point nudged 4 cm off the surface along its normal, so a face doesn't shadow itself.

In shadow the key light doesn't fall to nothing but to 0.4 × the albedo squared. So the shade stays the shape's own colour, only deeper, as if the light had soaked into the plastic. A plain shadow would turn the dawn act's pinks slate grey.

The glass casts a third of a shadow, which gives a lighter pool on the floor and a lighter shade on the bodies behind it. The key light's depth pass draws it separately (pass 3 of s1_shapes) through object.frag. That shader sees that the camera is the light (uViewProj = uLightVP) and returns at once, after discarding two of every three diagonals of the map's texels. keyShadow's 3x3 taps lie 2 texels apart, so their diagonal offsets, 2 (i + j), land on each of the three diagonals three times. Wherever they fall, they average the pattern to exactly a third. A pattern with a period of 4 would leave a ripple of up to 0.06 that crawls as the light goes round. That's stripes 2 on and 2 off, or a 2x2 checker, which also gives half a shadow. The solids keep the plain depth program (shadowsh, pass 0).

3.6. The sky (sky.frag)

The backdrop is one full-screen pass, drawn first with depth off: a gradient from the act's bottom colour to its top colour, a glow round the origin made of two exponentials, and twinkling stars. far.vert (the underwater backdrop's vertex shader) hands it each pixel's view ray, so the sky is fixed in the world and turns as the camera flies its loop. That also suits the TAA and the motion blur, which reproject a pixel with no depth as a point on the far plane. The gradient follows the ray's elevation, from -0.3 to 0.45, so the level logo shot and the shots looking down on the origin both keep a horizon. The glow follows the angle from the origin, so it stays round and sits on the seed before the bang.

The stars are a 3D lattice of cells 1/180 across, cut through by the sphere of directions. One cell in a hundred is lit, as a soft ball round the cell's centre, and it looks as wide as the sphere passes near that centre. That gives stars of many sizes, spread evenly in every direction, a few hundred of them in any view. Each twinkles at its own phase, and they look the same on screen at any resolution.

The glow's spread follows its brightness. It's exp(-4 d) at the bang's flare (brightness over 1.4) and tightens to exp(-8 d) as it calms (0.6 and under). It goes back to exp(-4 d) for the faint glows under 0.1, the title's and the end card's. So through the hooks the glow is a warm hotspot in the middle of the crowd, where one width for everything would lay a milky haze over the whole frame. In the collapse it's a tighter halo round the shrinking crowd.

3.6.1. The dark, the shock front and the outro

Scene 1 uses uAtmo for three things. It's Scene 2's sky ramp, a field of the Frame block that Scene 1 has no other use for.

First, before the bang the universe is dark. The whole sky, glow included, sits at a quarter of its level, so a lone seed with a tight halo hangs in a near-black void with faint stars. The bang is then a hit from dark to light.

Second, on the bang uAtmo runs from 0 to 1 over the 0.4 s after the kick, and sky.frag draws the shock front. That's a ring racing out from the seed's direction, with a sharp outer edge and a short trail inside. At the front is a bright band whose rim glows rose white, (4, 2.8, 3) × its strength, the dawn act's colour. It's the bang's flash, sweeping across the frame. A longer trail would light the whole inside evenly, as a hard flat disc. As it passes, the ring bends the sky like a lens, pulling each ray in toward the seed. So the origin's glow is dragged out into the rim and the stars swim through it. It weakens as it grows and leaves the frame in about 0.2 s. Behind it the light ramps back to full over 0.25 rad. Ahead of it the dark only creeps up (0.6 × uAtmo), so outside the ring the light only ever grows, and floor(uAtmo) holds it at full from then on. The origin's glow flares for 0.8 s after the kick. Its wide halo lasts about 0.15 s, and the crowd's colours show within half a second of the kick.

Third, over the outro uAtmo runs from 1 to 2 in the 3 s before T_BLACK (46.2-49.2 s). The sky, its glow and its stars go down to black with it, while the seed, which object.frag draws, goes on shining alone. The world goes dark round the star the camera has backed away to (The Catmull-Rom camera). At 1 nothing changes, and the end card sets that value itself (The end card).

3.6.2. The finale's sun

Behind the title and the end card a synthwave sun stands on the horizon, directly behind the origin from wherever the camera is. It's a disc 0.4 rad (23°) across, gold at the top and hot pink at the bottom. Its lower part is cut by bars that thicken toward the horizon, which hides the part that has set. The bars are a quarter of a radius apart, each with a gap of 0.5 × (0.6 - y) of that, with y going up the disc from -1 to 1. A pink haze lies along the horizon and round the disc. The sun is blended over the sky at about the sky's own level, not added, because added it would wash out to a pale peach in the shoulder of the filmic tonemap. The floor (ground.frag) reflects it the way a wet road would, as a narrow streak running down from the horizon under it.

The sun's height over the horizon and its presence travel in uSun, which no Scene 1 shader has another use for. render_scene1 ties both to the title's presence (lv, which also dims the glow). As the cubes gather, the sun's centre rises 0.32, from 0.24 below the horizon to 0.08 above it, and it sets again as they scatter. No act before the finale sees it or its haze. The end card sets it over its five bars, from 0.1 to -0.14, under its fade. The finale's sky above the horizon (SEQ_TOP) is a deep violet, (0.40, 0.20, 0.85), so the pastel letters and the blob stand out against it.

3.7. The additive synthwave grid (ground.frag)

The grid is drawn with additive blending (GL_ONE, GL_ONE). Between the lines it's black, which adds nothing, so only the lines glow and the void shows through around them. A fine and a coarse set of lines, 2 m and 10 m apart, are anti-aliased with fwidth, pulsed by a travelling sine and faded with distance. The grid is depth-tested, so bodies hide it, but it's drawn with depth writes off, so it never hides them. It lies 4.5 m below the origin. It fades in over the 6 s from 2 s after the bang, scaled by the energy, and out again through the collapse.

The floor takes the key light's shadow too. Under the crowd the grid lines dim and the floor itself darkens, as though it had been catching the light. The additive pass adds a negative amount there, -0.6 × uBottom × (1 - shadow), which works because the HDR target is float and its blending is unclamped. Soft pools gather under the shapes and drift with them, and on the end card the letters' shadow lies on the floor behind them. Bodies below the floor are farther from the light than the floor is, so they cast nothing on it, and the floor isn't a caster, so it can't shade itself. The lookup is keyShadow(), written identically in object.frag and ground.frag so that it packs once.

The bang's shock front runs out over the floor before the grid fades in. It's driven by uAtmo going from 0 to 1 over 0.4 s, as in the sky (The dark, the shock front and the outro). The lines light up behind a front racing out to 60 m. They're brightest at the front and dimmer behind it (e^(0.12 x), with x in metres from the front, negative behind it), and the whole flare fades as the front slows. So the grid is first seen as the shock runs over it out to the horizon. s1_draw draws the floor while the shock is under way and skips the shadow taps while uGridFade is still 0. For those 0.4 s the frame costs what the floor costs from 9.7 s on, about 5 ms more on the UHD 620 at 2160 lines.

3.8. The Catmull-Rom camera

camera_at(t) samples a looping Catmull-Rom path through 8 control points (mc_spline_sample(..., loop=1)), picked by hand at varied radius and height. It goes once round the loop over the scene, on the warped clock. The path scales out from a tight framing on the dot (0.35 of its size) as the universe expands, and it bobs gently, while the field of view breathes between 51° and 55°. For the finale the camera blends (lerp) the eye and target toward a fixed head-on view of the logo plane and then lets go back to the fly-through. The blend eases in and out with smoothstep around T_LOGO_IN and T_LOGO_OUT. The view is aimed 1 m above the middle of the logo, so the title sits a little below the centre of the frame. The blob floats in the sky above it and the grid lies below.

Through the collapse, from T_COLLAPSE to T_BLACK with a smoothstep, the camera eases from its flight to a second fixed pose. The eye is 25 m out, looking about 15° down past the seed, so that the seed ends up where Scene 2's sun stands on the screen (uSunUV, 0.68, 0.72). Over the last second it stays within 3 px of that at 1080p. The crowd balls up into the seed as the camera backs away, and the seed ends as a star about 15 px across. That's as wide as the disc of the sun that rises in its place (see Scene 2).

On the bang the camera's aim jolts. A damped pair of sines (55 and 70 rad/s, e^(-7 t)) knocks the target a few degrees off, upward first, and it shudders back over half a second. Knocking the aim moves the seed in the frame, so the jolt shows. A shake of the eye round a target fixed on the seed would keep the seed still and go unnoticed.

3.9. Exposure, bloom and fades

The engine's renderer finishes the picture with an HDR target, bloom, a filmic tonemap, a grade, TAA and grain (see The renderer and its post stack). Scene 1 and the end card render at 2160 lines, 200% of a 1080p picture, for the reasons in the engineering notes. render_scene1 moves a few of the post's settings over time:

  • The bang is an exposure kick of +1.5 before the tonemap, falling off as e^(-12 t). So the seed and the burst flare up, while the dark lifts only as the shock front runs out through the sky and over the floor. The flash is the shock's rim sweeping the frame. A white flash after the tonemap would lay a flat grey veil over the dark for a few frames before the ring brought the light.
  • The bloom is high through the intro (0.32, against 0.07-0.11 in the hooks) and falls to its running level over the second after the kick. Through the hooks the bloom and the exposure follow the energy and the beat.
  • The master fade (post.fade) brings the picture up over the first 2.5 s. At the end it holds until a beat after T_BLACK and then dims the star, to about a quarter by the cut at 50.3 s.
  • While the blob is present it lifts the bloom a little and softens the vignette.

3.11. The blue blob

A friendly round blob drifts in the background. It's a smooth ball that blob.vert makes from gl_VertexID: a cube with 48x48 cells on each face (82,944 vertices, the same count as the planet), pushed out onto a sphere. The end card's arms need that many cells, and elsewhere they only smooth the outline. It wobbles along its normal by a 3D noise field, scaled by uWobble (1 in Scene 1), and breathes a little. Its own small shader draws it (blob.frag). Its colour is the first of BLOB_COLORS, blue (0.15, 0.40, 1.0). In Scene 1 it's a deep, glossy blue, brightest where it faces us and darker toward its edge (0.25 + 0.65 cos θ), so that the round body and its lumps show. Its crown catches the bright window of the sky above as a soft glint.

Its eyes are two dark flattened spheres (blob_eyes), set low on its face, 0.37 of its radius up and 0.96 out, clear of the lumps. Each has a white catchlight, a small bead sunk into the front of the eye, up and to the left. Only its cap shows, so it stays on the eye from any side. The eyes are instances of the ball in the shapes' lists, so whatever is in front of the blob hides them properly. The title's blob, the blob in Scene 2's deep and the one on the end card all have these eyes. blob_eyes takes a size: 1.2 on the title and the card, and up to 1.11 in the deep.

A shut eye is a dark lash line with a faint rim. As the eye squashes down to a sliver, a lid grows just behind its front. The lid is as wide as the eye and (1 - openness) × 0.22 of it high, in a blue (0.25, 0.42, 0.75) that doesn't glow, darker than the body. The eye, its lid and its catchlight are drawn in one loop. In the deep, a bare sliver would be lost among the jelly's dark caustic patches, and a pale lid would look like the white slits of a visor.

It appears three times (blob_presence), each more strongly than the last. It gives a faint glimpse as hook A arrives (14.0-17.0 s), drifts through the breakdown (23.7-30.2 s) and, most of all, floats above the MICROCOSM logo in the finale (33.0-43.0 s). Its eyes show only in the last two. At T_LOGO_IN (32.7 s), while it's out of sight, it moves 12.5 m higher and 14 m further back. So it looks a little smaller, and it sits in the sky over the title with its wink in full view, instead of glowing behind the letters and washing them out. It fades in with GL_CONSTANT_ALPHA blending at its presence × 1.15 and is nearly opaque at its fullest (0.98), so the sky doesn't grey it. It stays depth-tested, so near shapes hide it. While it's present it lifts the grade a little, with a bit more bloom and a slightly softer vignette.

Near the end of the finale, at T_WINK (39.4 s), the blob winks: its right eye closes under its lid and opens again over half a second while the left stays open. It comes back, the same blue, in the deep of Scene 2, where it has no face until the scare is over (see Scene 2).

4. Scene 2: from orbit to the deep

Scene 2 has its own look and its own music. The camera falls from orbit to the sea, levels off, and then trails the blob as it wanders across the seabed. The scene is 54 bars long and runs from 50.3 s to 154.8 s of the show. The table below gives each beat in scene time, the seconds since the hand-off from Scene 1 (the t that render_scene2 gets), and in show time. The times inside the table's descriptions are scene times too. The music is described in the soundtrack's Scene 2 section.

From the breach on, the act runs on a story clock of its own. uw_u() turns scene time into story seconds since the breach, and every cue under water (WHALE_IN, ENC_PEAK, FLIGHT_IN and the rest) is a constant on that clock. The story's 40 bars play over 44 bars of the show, some parts slower than others, as Timing explains. Where this section gives a cue in story seconds, it gives the show time as well. The two shaders that need story time, the reef bed and the big school, get it from uw_u() (U_STORY) instead of uTime.

Beat Scene time Show time On screen
Space 0-9.7 s 50.3-60.0 s The sun comes up first out of Scene 1's black, where Scene 1's last star went out. The fade-up works like an eye adjusting, with the exposure opening over 2 s under a 0.4 s fade. The brightest stars come next, and then the planet: blue and green, a third of it in night, ringed by its thin blue air and turning against black space. The stars are steady, and a few bright ones have a glow and four spikes. The band of the galaxy runs behind them, warm at its heart, blue at its edges and faintly pink where its gas glows. A sharp-edged sun sits off to one side. The grey moon hangs low to the right of the planet, nearer to us and in the same phase. The camera drifts slowly closer, and the moon slides outward against the planet.
Dive 9.7-14.5 s 60.0-64.9 s The camera accelerates straight down at the planet. The planet swells to fill the frame and its mountain ranges catch the light, while the sun swings round behind us and the atmosphere's haze builds. The moon swells too and sweeps out past the right edge of the frame. Space only turns from black to blue sky in the last 1.2 s, as the planet comes to fill the frame. Any sooner, and the planet would hang in a blue sky like a toy ball. A half-time heartbeat kick comes up under the dive, and the pad, quiet out in space, rises with it until the splash.
Cloud break ~14.5 s ~64.9 s The picture flashes to full white as we punch through the cloud deck.
Sea 14.5-19.4 s 64.9-69.7 s We fall toward a wavy ocean under a sky that deepens overhead, and the fall slows and levels out over the swell. The sun lays a glitter path on the water, and a riser climbs underneath. Over the last few metres the lens rides the rise and fall of the waves, a hand's breadth above the water.
Breach 19.2-19.5 s 69.5-69.8 s The lens goes under without a cut. A waterline sweeps up the lens, with a bright film along it and the meniscus's shadowed lip just under it. Below the line the picture already shows the sea we're entering, and its colour grade comes in with the line. The music's filter slams shut.
Deep 19.4-66.6 s 69.7-117.0 s The lens comes in high, in bright, clear water just under the surface, with the bubbles from its own entry rising past it, and over 3.5 s it sinks into its place behind the blob as the water darkens. The blob, which has no face yet, swims a wandering path across the seabed, and the camera trails it from a distance that keeps changing. Until the encounter gets near (~60 s), the camera stands 4 m further back and is turned 17° left. That puts the blob low in the right third, with the seabed and the open water beside and above it. Everything the blob passes is placed free of collisions, so the blob, the boulders and the fish never intersect. Three things build up together. The water clears from murk to sharp (by ~27 s), the blob turns from milky to clear jelly (by ~32 s), and the shadows, the occlusion and the PBR come in (by ~45 s). A realism push layers in finer detail textures, normal relief, sharper speculars and extra caustics. And there's more on screen: more boulders, which gain crags and fine geometry over time, more circling schools of fish and more swaying weed. From ~26.5 s a blue whale comes out of the clearing water far ahead, high in the frame, and crosses over the blob's trail, big and nearly side-on as she passes. Her calf swims 5 m under her near flank. It's the same sculpted mesh at 0.4 of her size, drifting forward and back along her with its flukes out of step with hers. At ~41.5 s the camera has eased 10 m back from the blob and looks up at the whale and calf against the bright water while the music opens out. She is left behind when the blob turns away (~46 s). From ~50 s a swarm of moon jellies drifts above the blob's way, bells pulsing and tentacles and frilled arms trailing. Cyan canals brighten across the bells as the water darkens briefly, and the blob passes under them. Some surfaces are textured and some are procedural. A few bubbles of air (14 of them, like silvery beads) rise from the blob.
Encounter 66.6-78.8 s 117.0-129.1 s A big, dark, toothy predator comes out of the murk ahead and stalks straight at the blob, which swims on without noticing. The camera hangs back and holds still. The lens slowly tightens while the image goes cold and dark and the vignette closes in. At ~71.3 s the predator lunges, jaws wide open around a black mouth, and at ~72.6 s it misses by a hair. It skims over the blob's shoulder and sweeps past the lens with its mouth wide open, and its jaws snap shut on the miss as the camera jolts. Only then does the blob panic. It flinches and quivers, then bolts, and its path whips round in a hairpin. The predator turns and gives chase, seen side-on from a high camera, until it's outrun, gives up and turns back into the deep. Through the stalk the music thins out. From ~65.8 s the anthem drops to a heartbeat, a half-time kick and short roots, with the supersaw, hats and snare gone and the pad's filter shut. One kick lands on the near miss. As the blob bolts, a beat of snare sixteenths brings the full anthem back for the chase (~73.5 s).
Transformation 78.8-89.3 s 129.1-139.7 s Once it's safe, the blob slows to a drift. The camera cranes down into an orbit round it and circles it a little under once (0.88 laps), easing in and out. As it orbits, the blob's eyes, its first since Scene 1, grow in from nothing to their Scene 1 size, wide open and turned to meet the camera. Meanwhile its lumpy surface rounds out into a sphere, as the wobble in blob.vert eases to near zero.
Flight 89.3-102.0 s 139.7-152.3 s The blob is still panicking, and it bolts further and faster all the time. Its acceleration keeps building (quadratic plus a cubic kick) and its wander widens, so it spirals outward into fresh ocean instead of looping. From ~90.8 s the camera eases down into a wide chase, 11 m behind the blob along its path and 3 m above it. It swings 23° to one side and then the other, and the near reef streaks past under a longer motion blur. As the camera swings out, the blob glances back over its shoulder and shows us a 3/4 face, and it turns away again as the camera swings back behind it. On its way it uncovers more and more complex structures: reef formations (tight clusters of boulders) and kelp forests of broad golden fronds rising toward the surface. In the coral gardens, staghorns, brain domes, tables and sea fans are massed into patches on a bed of their own rubble and growth. There are scuttling crabs and circling schools, and the kelp, the coral heads and the fish stream past close below and beside the lens. At ~95.6 s the blob bolts straight through a big school of 300 fish. The school parts round it, a channel opening ahead and closing behind, and we fly down the channel at the blob's height. The reef is at its fullest here. By now boulders and weed have come up across the whole seabed as well. They stand on a jittered grid laid over every bit of sand, kept clear of everything else and grown in out of the camera's sight, so even the far sand the flight reveals is never bare. Now that its eyes have grown, the blob's way is also lined with landmarks, six of each kind. A rock outcrop is a huge core boulder with big ones locked into it all round and two perched on top, so it looks like one mass, clearly bigger than the reef formations. A coral reef is a dense group of touching heads in two main colours, ringed with kelp, and a kelp thicket crowds round a coral heart. Each landmark sits off the flight path, clear of the blob, the camera and the schools, and grows in over whatever stood on its spot while the whole site is out of the camera's view. From ~96 s on, most frames show three or more. The music lifts with all this: from ~87.1 s, 2 s before the blob takes off, the anthem's supersaw swells up to the outro's downbeat (~98.7 s).
Burn-out 102.0-104.5 s 152.3-154.8 s The picture over-exposes, blooms and burns out to pure white over 2.5 s (S2_END_FADE, mc_post.white) while a noise riser swells over the music's outro. Scene 3 opens from that white.

4.1. Two frames, a flash and a waterline

Landing a camera on a sphere and then standing it on a flat sea takes two different coordinate systems, and joining them properly is a lot of trouble, so the demo doesn't. It runs two separate frames and cuts between them on a flash. Up to the cloud break it's a planet frame, a ball scaled to PLANET_R at the origin. The camera dives in from about 150 units out to 2.5 units above the surface, and the lens widens on the plunge. After that it's a flat-sea frame, a plane at y = 0, for the fall to the water and everything under it. A Gaussian flash in the post (cloudFlash) covers the switch. It goes fully white for the frame of the cut, so neither frame shows across it.

The fall (camera_sea) starts at 55 m and slows as it nears the water. Its height is 55 (1 - u)(1 - 0.74 u), never under 12 cm. It levels out, pitching from -41° to -7°, and glides forward, reaching about 8 m/s at the end. Over its last 6 m it rides the swell. sea_level evaluates ocean.vert's sines at the two grid vertices on the line the camera falls along (x = 0) and blends them the way the rasterised surface does. So the lens follows the surface that's actually drawn there, and it's a hand's breadth above it as it goes under. Above a mean sea level instead, the lens would dip in and out of crests that rise more than 2 m, and the glints would strobe.

The crossing takes 0.17 s either side of S2_BREACH (S2_DIP), with no cut. Each of those frames draws the sea above (the sky and the ocean), copies it to texture unit 9 (jelly_copy, the whole frame), clears, and draws the sea below. The underwater act is held at its first moment until the breach. Then dip.frag lays the sea above back over the picture, above a wavy waterline that sweeps up the lens. A bright film of light runs along the line, with the meniscus's shadowed lip just under it. Near the line the image above is lifted a little, and the underwater grade comes in with it. The frame's first camera, the one the TAA's history and the motion blur follow, is the underwater one, so that it matches the depth buffer. Drawing two seas costs 1.1-1.7 ms more for those 0.34 s, which still keeps them under 11.2 ms. Over the fall and the first seconds under, the mean luma never changes by more than about 5 levels (out of

  1. from one frame to the next.

The copy's texture (jelly_tex, RGBA16F, 37 MB at 2880x1620) is made again whenever the renderer's size has changed. The size changes at the cut, from Scene 1's 200% to Scene 2's 150%. So render_scene2 checks the size on Scene 2's first frame and makes the texture again there, where the picture is near-black (the fade-up) and the renderer is resizing anyway. Otherwise the texture would be made at its first use, the dip, and that frame would stall in full view.

Going under (camera_uw), the lens enters level and 6.5 m higher than its place, in the brighter, clearer water near the surface. Over 3.5 s it sinks into its place behind the blob as the water darkens. The 40 bubbles from its entry are anchored in the water where it came in, and they rise past and away over about 3 s. They're beads of air like the blob's own (see Bubbles and marine snow), 0.7-1.7 cm across for every metre they are from the lens. So going under looks like a burst of silver instead of empty teal.

4.2. The underwater camera

camera_uw is the picture's camera. It's camera_at2's trail, reframed (below), plus the dive's entry (above), the glance up at the whale (see The water's light) and the flight's chase. The seabed's props are placed and timed against this camera too (see The seabed and its props), so the boulders keep out of its way and every prop grows in out of its sight.

The trail (camera_at2, under the water) follows the blob 8-14 m back and 3 m above it, along its level heading. blob_frame gives that heading as the direction to where the blob will be 0.12 s later, with the height dropped. The level heading matters where the blob all but stops, at the far end of a loop in its path. There its full heading would be mostly its gentle bob, and the trail would rise high above it and look steeply down. Kept level, it stays low, and the whale leaves the frame whole, by its right edge. camera_uw reframes the trail in three ways:

  • Until the encounter draws near, the camera stands 4 m further back and is turned 17° left and 3.4° up. That puts the blob low in the right third, with the seabed and the open water (and the whale's pass) beside and above it. It eases back onto the trail over story seconds 44-48 (ENC_IN - 12 to ENC_IN - 8, 107.1-110.4 s of the show). Trailed dead centre and half the frame high, the blob would be a pale, faceless lump in murk for the act's first 20 s.
  • Through the transformation (story seconds 72-84, 129.1-139.7 s) the camera orbits the blob. The orbit is blended in around the blob in bearing (the short way round), horizontal distance and height, so the camera cranes down and in from the chase shot, from 17.5 to 8.9 m. It sweeps round to the bearing the trail takes at TRANS_END, going whatever part of a lap that takes plus half a lap, wrapped into 0.5-1.5 laps (0.88 here). It eases in and out (smoothstep), so the hand-back to the flight is the same blend undone. The view turns at most 0.85° a frame at 60 fps. A straight-line blend toward an orbiting point would cut a chord through the swing and swoop in until the blob filled the frame. With a lap or more, the reef would whirl past. A wider orbit on a narrower lens wouldn't slow the background, because the background turns with the view and a narrower lens spreads each degree over more pixels.
  • From FLIGHT_IN + 2 (story second 86, 141.1 s of the show), over 3 story seconds, the flight's camera eases down into a chase. It sits 11 m behind the blob along its path and 3 m above it, 75° wide, looking down past the blob at the reef 2.2 m under it. The direction back is the mean direction to the points 8-16 m back along the path by arc length (in 0.03 s steps). So the distance never changes (11.40 m), and a bend or a burst of speed turns the view smoothly (at most 2.35° a frame at 60 fps). A single point a fixed straight-line distance back would jump where the path bends. The camera swings 23° to one side and then the other, on the same sway that the blob's glances back follow (sin(0.4 u + 0.5)). So we see the blob 3/4 from behind, with its eyes showing. On the path the camera stays inside the tube the props are cleared from, and the big school parts round it. The kelp, the coral heads and the school stream past close below and beside it. The image motion builds up into the school's parting, from 6 to 19 px a frame at 960x540.

Over the flight's first 5 story seconds (139.7-143.4 s), the reach of the camera motion blur opens from 0.25% of the screen each way to 1% (mc_post.blur). The close kelp and coral cross the frame at 30-45 px a frame at 60 Hz. With only about 5 px of blur they would keep sharp edges and step from frame to frame, and with the wider reach their edges smear along the motion. That costs about 2 ms more in the composite there. The shutter is 120°, since a wider one smears the school that swims along with the camera. The shutter limits the blur more than the reach does, so the near seabed still strobes a little at about 151-152 s.

We checked every 5 ms of the act against every prop, every fish and a CPU copy of the big school. Nothing comes within 2.77 m of the lens (the closest is a circling fish, during the stalk), and nothing of the big school within 5 m.

4.3. What draws it

Scene 2 has its own shaders, all of them procedural, and genshaders bakes them in like the rest. dip.frag draws the crossing's waterline (see Two frames, a flash and a waterline). The others are described below, from space down to the reef, followed by the blob, its bubbles, the moon jellies and the placement of the seabed's props.

4.3.1. Space and the sky

space.frag (full-screen, sharing sky.vert) draws deep space and cross-fades it to a blue sky as its uAtmo climbs, so one shader carries us from orbit down through the atmosphere. Space is near black, with a field of steady stars: one cell in 40 of a grid 200 cells to the screen's height holds a star. A star can sit anywhere in its cell. Its brightness is a steep power of a hash, so most are faint and about 2% are bright, and its colour runs from red-orange to blue-white. The stars don't twinkle, since there's no air to twinkle through. One cell in 30 of a grid eight times coarser holds a brighter star, with a glow and the lens's four spikes. The sun is a sharp disc 14 px across at 1080p, darker toward its limb. It sits in a tight glare that falls off with the square of the angle, plus a faint veiling glow. Those distances are measured in screen heights, so the glare is round.

A faint band of the galaxy crosses the sky diagonally behind the planet, clear of the sun. Its unresolved starlight is an fbm stretched along the band, warm white at the heart and bluer at the edges. A finer fbm, stretched 4:1 along the band, splits it down the middle with dark lanes of dust, and more stars crowd it, one cell in 15 at its heart. Knots of faint pink glow along it, where its gas is lit. At its brightest it's only 0.05, so it shows as structure without fogging the sky, and it fades with the rest of space as the sky turns blue. The band is separate code and could be dropped on its own.

On the plunge the sky is laid out on the screen, pale at the horizon and deeper blue overhead. From the cloud break on (floor(uAtmo) = 1, under the white-out), it's laid out by the direction each pixel looks, through inverse(uViewProj). That's mix(uSky, deep blue, sqrt(3 × elevation)), a pale band at the horizon and blue from about 20° up. Its glow sits round the true sun (uSun), up to the left above the glitter path, and the sea mirrors the same sky (see The sea from above). So the glow stays put as the camera pitches, and sky and sea meet at the horizon in one colour.

4.3.2. The planet, its air and its moon

planet.vert makes the planet from gl_VertexID: 48x48 cells on each face of a cube, pushed out onto a sphere. In planet.frag, continents, oceans, ice caps and drifting cloud are all 3D fbm noise over the object-space direction. That's six octaves of value noise, each turned as well as scaled so that the lattice never lines up. Each octave is one fetch from the demo's 32³ lattice of random bytes. That lattice is vn3, the value noise every shader shares (see Engineering notes). One broad field warps the rest. That gives coasts that wander and break into islands, a deep ocean with a paler shelf along the coasts, forest or desert where the broad field says wet or dry, and bare uplands. The ice caps have a ragged edge and reach further down over land than over the deep sea.

The cloud streams in bands. Its field is squeezed north-south, pushed by the land's two fields, swirled into fronts and eddies by two fbms of its own, frayed by one six times finer and cut by a narrow step. It drifts, shades the ground under it (it's read again a step toward the sun) and is darker on its own side away from the sun. The cloud's three fbm fields cost about 2 ms at 1080p as the planet fills the frame.

The sun lights the planet from the backdrop sun's side, about 68° off the eye (uSun × (1.7, 1.5, 0.1)), so in space a third of the disc is night behind a soft terminator. On the plunge the sun swings round behind the eye with uAtmo, so no night side hangs in the blue sky and we fall into a sunny noon.

The air is Rayleigh scattering plus ozone, in closed form. air(s) is sunlight reddened over an air mass of 0.1 / (s + 0.1)². Rayleigh takes blue first, and the ozone's Chappuis band takes a fifth to a quarter more of the green and red. The extinction is (0.0073, 0.0176, 0.033) where Rayleigh's alone is (0.006, 0.014, 0.033), the column ratios of Bruneton's 2017 model. The light is white overhead, faintly warm at 30°, orange low down, red at the terminator and dark past it, and it lights the ground. Without the ozone, the low sun would turn yellow and then green against the air's bluish scatter, one more band of rainbow on the way down.

The air between the ground and the eye dims the ground by its transmittance along the view, e^(-τ / n·v), where τ is the Rayleigh depth straight up. It also adds the light it scatters on the way. That light is lit by air() at the ground's sun, but it takes only half the tint: (A + A.r) / 2. The other half is the red's strength as a grey, which is how the sky's own light would look once scattered again. The result is a blue haze that thickens to a pale limb, faintly warm at the terminator and gone at night. Red is the last channel to go, so the glow fades out where the sunlight does. The full tint would go wrong near the limb, where the scattering saturates every channel. There the air would send out the sun's own colour at each height of the sun, and air() runs through the whole sunset within about 0.2 of the sun's cosine. So each end of the terminator would wear a saturated rainbow sliver a few pixels wide. With the half tint the rim fades from the blue-white limb through a faint peach and lavender tip into the night.

Only the sea has a sheen, broad and faint and weighted by Schlick's Fresnel. It's a Blinn lobe of exponent 60, scaled by 5, to match the 0.17 rms slopes of wind-roughened water (Cox and Munk's figure at 5 m/s). A tighter, brighter lobe looks like plastic. Where there's no cloud, the land's height above the sea bends the normal the ground is lit by, through its screen-space slope. That's the surface gradient, taken from the fine derivatives, because the coarse ones show 2x2 blocks. Mountain ranges catch the sun on one side and fall into shade on the other, most of all near the terminator, while the air keeps the smooth normal. The footprint's area under that slope is floored at 10⁻⁹. Edge-on at the limb it reaches 0 over a sea whose slope is 0 too, and the TAA's history has no NaN guard, so it would keep the 0/0.

atmo.frag (sharing planet.vert) draws the same sphere 6% larger and adds it on with the front faces culled, so it only shades the ring round the disc. Each pixel's ray scatters 1 - e^(-t) of the sunlight, where t is the Rayleigh depth along the ray. That depth falls off exponentially with how high the ray passes over the ground, so the halo hugs the limb. It's 66 τ e^(-50 h), with h in shell radii, matched to the disc's limb. Near the limb the depth saturates every colour and gives a pale band. Higher up it saturates only blue, which leaves a blue fringe fading to black. The light it scatters is air() where the ray passes, with half the tint as on the disc. It's white on the day side, a faint peach at the terminator's two ends and nothing on the night side. Its sun turns with the disc's, so the two meet at the limb.

In the planet frame the post's lateral chromatic aberration scales as (1 - uAtmo)², so it's gone where the sky is blue and unchanged in space, over the sea and under it. At full strength it would pull red from the bright sky into the edge pixels of the planet's dark limb, as a red line a pixel wide.

The moon (moon.frag, drawn on planet.vert's ball) is a fifth of the planet's size. It sits 65 units out toward where the camera comes from, and 23 units off the camera's line of approach. As the camera drifts in, the moon hangs low to the right of the planet and slides outward against it, which is the shot's only parallax, since the stars are at infinity. When the plunge passes it, the moon swells and sweeps out past the right edge, never coming nearer the lens than 23 units. Its surface is dust over old lava plains: broad darker seas and mottled highlands, with a gentle relief taken from the mottling's own field. From this far the moon shows mostly through its albedo. A relief strong enough to read as rock makes it look like an asteroid, and the post's colour fringes run purple round it. It's lit by the planet's sun, turned the way planet.frag turns it, so the two show the same phase. With no air, the terminator is hard.

4.3.3. The sea from above

ocean.vert / ocean.frag draw the sea surface. ocean.vert makes a 900 m plane of 140x140 cells from gl_VertexID and raises it into a swell with a sum of four directional sines, and ocean.frag sums the same sines again per pixel, with their slopes. On top it adds five finer octaves of chop, each turned 2.4 rad from the last at 1.9 times its frequency and moving at deep-water speed, and a value-noise cat's-paw on the slope. The finest two octaves make the ripples close up. So near the eye the surface is smooth and lively, instead of showing the grid's 6 m facets.

Each of these layers fades out by fwidth where it would alias. The cat's-paw's 0.6 m cells fade between 0.25 and 0.8 cells a pixel. Any later, and through the fall's mid-distance they'd be 1-2 px across and sparkle like grains of salt. The variance of a faded octave's slopes, which is how far it would have tilted the surface, goes into the roughness instead (LEAN / Toksvig-style normal filtering). The sun's glint is GGX: D F / (4 n·v) n·l, with α² = 0.64 r over a base of 0.003 and Schlick's Fresnel at the half vector. With the filtering it gives a sharp sparkle up close, a smooth glitter path far off and clear ripple highlights in the skim.

The sea reflects the sky by Schlick's Fresnel (F0 0.02). It's the same sky space.frag draws behind it, glow and all, so the sea brightens round the glitter path. From 200 to 440 m the far sea melts into the haze at the horizon. Sky and sea meet in one colour before the edge of the 900 m plane can cut the sky in a line. The water's body runs from deep blue troughs to turquoise crests that the light shines through, more so looking toward the sun.

Foam breaks only on the higher crests (1.4-1.7) and thins out inside them, its opacity ramping over 0.45-0.75 of its noise. A finer octave frays it into lace, and it's torn into streaks along the wind, in broken patches a metre or two across. Its cells are about 2.9 by 0.8 m and drift downwind. Much larger cells give one soft blob per crest, like paint. The noise evens out where it would shrink under a pixel. Inside, foam is bubbles. A fourth octave of its noise, round and about 15 cm across, drifts with the foam and crowds and thins it. It uses two lattices turned against each other, because one alone shows square cells that look like pixels. The foam is brighter where the bubbles are packed, the water shows through where they're thin, and they fray its lace edges too. This octave also evens out where it would shrink under a pixel. The slope the foam lies on, the chop's normal, turns it toward the sun or away. The foam's bubbles cost about 1.4 ms a frame over the fall.

4.3.4. The water from below

water.frag is the underwater backdrop: the water along each pixel's view ray (see The water's light). It's full-screen, and far.vert puts it on the far plane and hands it that ray. The light it scatters brightens toward the surface and toward the sun. Shafts cross it, the caustic web's light carried down as sheets. The ray samples them six times, crowded into the near metres. They're sharp near the surface and blur with depth: the contrast is 1 at the surface and 0.75 at 10 m, and never drops under 0.4. They sway with the swell and fan down from above the frame. The shafts hang from the eye's path, smoothed over the half second around now, and give way to a broader, softer pattern when the eye flies or swings fast.

Looking up, the ray meets the surface from below. Inside Snell's window the whole sky and the sun are squeezed into a disc 97° across. Its rim splits into a thin rainbow, because each colour has its own critical angle (n 1.331 / 1.334 / 1.340). Outside the window, total internal reflection mirrors the dark water. Eight ripples on a geometric spectrum break both into moving facets, whose curvature focuses and spreads the light through each one. Each ripple fades out before it gets too fine for a pixel to hold. When the eye moves fast the rim softens, and the facets and the sun's hot spot calm down along with the shafts.

Specks of particulate drift in the water, fixed to their view directions. One cell in 25 holds a speck, each with its own size up to half a cell, and the larger ones are dimmer. So the small ones are sharp points and the large ones soft, dim discs, as if they were nearer and out of focus. Specks of one size and brightness would look like a stencil laid over the open water.

4.3.5. Sand and boulders

uwsurf.frag (with object.vert for the seabed and rock.vert for the boulders) shades the underwater surfaces. A per-instance material id picks the look: baked sand or rock textures, sampled triplanar, or a live procedural rock colour. So the scene mixes textured and procedural surfaces. The sampling is really biplanar: of the three planes it skips the one the surface faces least, fetch and all. It runs the shared physically based model of GGX, analytic IBL, detail normals and the water's extinction, with the caustic web folded into the sun's radiance (see The renderer evolves (the uQ ramp)). The seabed's base sand breaks up its tiling with tnt(), as the beach does in Scene 3. A second ramp, uReal, layers finer detail textures over the base, a 4x octave and then, as the ramp nears its end (uReal=²), an 11x micro-grain, while =uQ blends the whole thing up from a flat early look. Both ramps are complete by story second 48 (110.4 s), so the floor and the rocks gain a lot of definition over the act's first half.

The rock is a warm grey stone (0.20-0.50 baked, 0.16-0.42 procedural, brown-grey). Silt has settled on its upper faces as a pale, sandy film, deepest in the hollows of its relief, so the rocks belong to the floor they sit on. The silt is a constant colour, since at these sizes a silt textured like the sand looks no different. Underneath, the rocks are lifted by the sand's light bounced back up, which the analytic environment carries (see The renderer evolves). uwsurf.frag also lights the seabed and the boulders with the jelly's caustic (see The jelly).

Where the coral patches, the landmark reefs and the flight's last coral stand, the floor itself turns to reef bed, so the heads sit in one reef instead of on pale sand. For each 3.5 m square of the sand, the placement leaves two things: how much bed the coral heads around it lay down, and the time from which it may grow in (see The seabed and its props). The sand blends the bed of the four nearest squares bilinearly, up to 1, and grows it in over 4 story seconds. Two noise octaves fray its edge into the sand, because with one, a straight 3.5 m edge shows. The bed is rubble and crusts of growth, mottled ochre, olive and dusty rose and darker in its crevices, with a low relief of its own through the detail normal. Through the flight it costs 2.7 to 4.3 ms a frame at 150% on the UHD 620, for the sand's four fetches and five noises a pixel.

rock.vert (which feeds uwsurf.frag) gives the boulders progressive geometry. Three octaves of crags are baked into the rock at load by uwplace.comp, and the mesh's indices pick its vertices from that buffer. On top of those, a per-instance 3D-noise displacement grows along the surface normal as uReal rises, and its higher octaves fade in later. The rocks gain crags and fine relief as the scene goes on. A three-tap finite difference rederives the normal from the noise gradient, so the new geometry is lit correctly. Each boulder is also squashed and stretched per axis (0.7-1.1, seeded by its position) and dented by one broad noise octave that only ever pushes inward, so no two share a silhouette. Because the placement leaves headroom for this growth, the rocks still never touch anything. The shadow pass uses the same vertex shader, which keeps the shadows matched.

Each boulder darkens toward its foot, where it's bedded in the sand. Its centre stands 0.35 of its scale above the sand (uwplace.comp's seat), and a vertex's tint runs from 0.3 at 0.45 of the scale below the centre to 1 at 0.25 above. That's the shade, silt and wet where the rock meets the sand, which the GTAO, with a metre's reach at half resolution, barely draws under a convex rock. The tint rides in vColor, the albedo tint uwsurf.frag already applies, so the shadow pass is unaffected.

The sand darkens round each boulder's foot as well. For the sand only (uSandOnly), uwsurf.frag walks its square's list. For each boulder that's born, grown in and not yet overgrown by a landmark, it darkens all of the sand's light by up to half. How much depends on how near the point lies in the boulder's own frame. To work in that frame it undoes the boulder's turn (Rodrigues) and divides out its size and rock.vert's squash, so the shade is an ellipsoid that hugs the rock's outline. It's full at 0.6 of the rock's size, which is always inside the rock, and fades smoothly to nothing by 1.8, as 1 - smoothstep(0.6, 1.8, h). On the side away from the sun, where the cast shadow already darkens the sand, it's up to 15% tighter. Without it the rocks would look set down on the sand instead of bedded in it. The foot's tint stops at the rock's own edge, and the cast shadow lies on one side only and fades out past 35-40 m. With it each rock sits in a soft skirt, near and far, that follows the caustic web and doesn't cut across it. The shade dims all of the sand's light, because dimming the ambient alone blackens the web's dark cells beside a rock while its bright lines stay. Rock on rock, coral and weed get no contact shade. The sand's shade costs 0.6 to 2.8 ms a frame at 150% on the UHD 620.

uwbake.frag is the material baker. Once at load it renders four tiling textures into repeat-wrapped, mipmapped 2D textures: sand and rock for the seabed, plus a fish-scale skin and a whale skin. The two skins are sampled through real mesh UVs (below). The sand and rock are filtered 4x anisotropically, like Scene 3's tiles, so the floor and the rocks stay sharp where they slope away. The skins aren't, because on the scales it only sharpens the moiré (see Fish and the big school).

4.3.6. The animals' meshes

The fish, the crabs, the predator and the whale are all sculpted at load by the creature kit that also makes Scene 3's tyrannosaurus (jr.comp, described in The tyrannosaurus). Each is a smooth union of round cones, ellipsoids and flat fins in its rest pose. The fish's body is an ellipsoid whose section tapers toward nose and tail, and its fins are thin triangles. The whale's body is a loft, section by section. The predator's body is cut off at the hinges of its jaws, and each jaw is sculpted shut and then turned open to the rest gape fish.vert assumes (see The predator). Each animal is meshed by surface nets on a grid of its own. The cells are 0.06 for a fish, 0.07 for a crab, 0.015 for the predator and 0.014 for the whale, in model units, which the instances scale.

They all go into one shared vertex buffer. Each vertex carries the two bones that hold it, from among the body, the upper and lower jaws, their fangs and the fins. On the predator it also carries a mark for whatever lies inside the mouth. That's about 3,100 triangles a fish, 3,200 a crab, 22,000 for the whale and 60,000 for the predator. fish.vert poses all four, reading that buffer by gl_VertexID in the usual instance batches, and a per-draw flag tells it which animal it's posing. The fish and crabs swim with a wag that travels down the body, the predator works its jaws as well, and the whale beats up and down.

4.3.7. Fish and the big school

fish.vert / fish.frag draw the fish. The body tapers to points at the nose (+Z) and the tail (-Z), and a forked tail, a dorsal and two pectorals give it a proper silhouette. The vertex shader wags the tail with a sine wave down the body. The fish swim in circling schools, seated at even angular spacing on the ring so that school-mates never collide. They point nose-forward along their direction of travel (the yaw is atan2 of a one-step look-ahead), so the fin trails at the back and they move the way they face. They're shaded procedurally, with a dark back, a light belly and faint banding, plus a specular glint and an iridescent Fresnel that both tighten and strengthen as uReal rises.

The body samples the baked scale texture at coordinates taken from its rest-pose position, cylindrically round the +Z axis, so the scales stay on the body as it swims. The texture drives the albedo, the scales' relief (as a detail-normal height) and the roughness, all blended in by uReal, so early fish look procedural and late fish are fully textured. As a scale shrinks from 6 to 3 pixels across, the scales are eased to their mean, the texture's last mip (scales(), by the UVs' fwidth). Any finer and their rows beat against the pixels. The beating is faint in the albedo and strong in the detail-normal relief, whose slopes grow with the frequency, and mip-mapping alone doesn't stop it.

scales() takes the footprint's scale as well. The predator's scales are three times finer than a school fish's, and they're eased to their mean from 18 to 9 pixels across. At those sizes their fine, even net looks like chain mail and crawls as the predator sweeps past the lens. So its flank is dark slate, and its scales only show where they're big, on the head as it passes the lens. The flank gets a quiet texture (0.8-1.2 of the albedo, with a faint relief of 0.02 + 0.04 uQ) and the fins get none, so they show only the rays of their spines. Where the scales are faded, the rim light their relief would catch on average is added back, 0.1 of the rim colour, so the skin keeps its brightness as they come and go.

The fins have their own bone and are shaded apart from the body. Each is a thin membrane in the fish's tint over rays fanning out from its root, turned to the viewer and lit through from behind. It's brightest looking toward the sun (a forward lobe after Barré-Brisebois) and has none of the body's scales or sheen.

Besides the circling schools there's a big school of 300 small fish, holding station round the blob's path where the blob passes at story second 92.5 (BIG_U, 146.0 s of the show). The blob bolts straight through it. The fish dart aside and a channel opens, the camera flies down it, and it closes behind. fish.vert places the school itself (uK's 32) as an analytic flow round the known path, with no flock to simulate. Instance i is a slot on Vogel's spiral (the golden angle round, the square root out), in four layers of 75 fish 1.1 m apart, over a disc 16 m across. The disc is centred on the last school record uw_world writes, and all of it lies under the camera's way.

Each fish finds its closest approach to the path from the nearest of 26 of the path's samples in the placement's buffer (binding 0) and how far along the next segment it lies. From 1.5 s before that moment it darts away from the path at right angles, by 5.5 m less a fifth of its distance from the path. An outer fish starts 0.15 s sooner for every metre out, so none catches up with another. From 2.5 s after, once the camera has passed, it drifts back. Each fish heads where it moves, on top of a common swim, and its model matrix is built in the shader.

We measured the school offline over the act, with a copy of the shader's maths in 0.01 s steps. The fish keep 5.55 m from the blob's centre, 5.1 m from the camera, 1.96 m from every prop's reach and 0.98 m from the circling schools' fish. As ellipsoids of a fish's length and height, they never overlap. The school is one instanced draw, made only past story second 60 (119.9 s) and only while its disc is in the frustum. It appears unseen, with the camera turned 136-176° away. It casts no shadow, since that would cost the whole draw again. It costs 4.4 ms a frame at 150% on the UHD 620 with the school in view and 9 ms inside it.

The crabs (sculpted, drawn with the fish PBR shader) are small, reddish and eight-legged, with no scales. They turn up on the floor in the second half of the act and are kept clear of the boulders.

4.3.8. The whale

The whale (sculpted, posed by fish.vert, painted by whale.frag) is a blue whale about 30 units long. Its body is a loft of cross-sections. There's a broad, flat, U-shaped rostrum with the throat sagging below it, the deepest girth just behind the flippers, and then a long, slender tail stock, flattened side to side. It has swept, notched flukes, two slim flippers thick enough to show edge-on, and a smooth back with no dorsal fin. The vertex shader swims it the way cetaceans swim, with an up-and-down beat. A slow wave runs from head to flukes, growing down the tail stock, and the normals bend to follow it.

The skin is painted from the rest-pose position (the angle round the body and the distance from the flukes to the snout), so it moves with the animal. It's countershaded blue-grey, mottled with lighter spots and darker blotches. Long throat grooves run back from the chin, the mouth line sweeps down to a small eye, and the blowhole is on top. The baked skin texture adds fine relief and roughness as uReal rises.

The whale makes one slow pass in a lane of its own at z -42, which the props and schools keep clear of at all times. So it never touches the blob, the rocks or the fish. The pass runs 150 m along +X from WHALE_IN to WHALE_OUT, story seconds 4 to 44 (73.1-107.1 s of the show), about 4.4 m/s. It's high in the water, 3 m up at the top of its arc and 5.5 m under the surface, and it comes and goes out of sight and out of the sun's map. It first shows at about 76.5 s, some 90 m off, coming into the upper half of the frame at its left edge as the water clears. It drifts across above the blob's trail, from three-quarters on to nearly side-on. At the held glance up at 91.8 s, the camera is about 48 m from her centre (see The water's light). It's left behind as the blob turns away: she is still visible around 95.3 s and out of frame by about 97 s. For roughly 20 s she is the centre of attention. Where the whale can be seen depends on the blob's heading, which the camera follows, more than on the whale's own timing.

Seen that large at 1080p, three of its details need care. The throat grooves fade out as they shrink toward 4 pixels apart (fwidth 0.1-0.25), since finer grooves crawl as a knitted grid of bright dashes. The caustic web is at 0.4 on the whale and off on its fins, because seen from below, the web's cells line up along the curved back as evenly spaced dark bars. And the fins keep the flat diffuse look (mix(flatc, lit, uQ × body)). The near flipper, seen end-on from below, would otherwise catch the IBL's grazing sheen and the rim as a pale, stair-stepped teardrop at its root. Instead it shows as a soft, lighter patch on the flank.

4.3.9. The predator

The predator (sculpted, drawn with the fish PBR shader) is a big, lean, menacing fish with a forked tail and a tall dorsal sail of spines with a zigzag membrane. It's dark, scarred and pale-bellied, with pale, glowing, slit-pupilled eyes. It has no snout, and its front ends where its jaws begin. The two jaws are deep and rounded, the lower one longer (an underbite), and each has four long, uneven fangs a side, raked back. The upper fangs sit on the rim's inner edge and the lower ones on its outer edge, leaning out, so with the mouth shut they cross outside the upper lip. On the middle of their rim, the lower fangs' tips would lie by the upper jaw in the rest pose and take its bone, and posed they'd stretch into bars across the mouth. The jaws open on a wet, near-black gullet.

The kit cuts the body at the hinges, sculpts each jaw shut as a hollow shell and opens it to its rest gape the same way fish.vert swings it, so the jaws close the way they were made. uGape swings the upper jaw and its fangs up and the lower jaw down about those hinges (pred_gape). While the predator stalks, the jaws are ajar at the sculpted pose, 0.55, with the fangs on the rims. From a second before ENC_LUNGE to ENC_PEAK - 0.35 (120.9-122.6 s of the show) they're flung open to 1.55 (66° each), a tall dark maw ringed with fangs. The predator sweeps past the lens with them wide open, its maw filling the frame at the near miss (122.9 s), and they slam shut on the near miss's kick, from ENC_PEAK to 0.15 s after it. Opened only to 1 (43° each), the jaws look like a toothy box, and shut any sooner they close before they fill the frame.

A vertex within 2 cm of a jaw's hollow is marked as inside the mouth by adding 256 to its bones word. fish.vert reads the two bones from the low bits and passes the mark on, and fish.frag paints such a vertex as the gullet, a wet, near-black red with no eye decal, and cuts its lit colour to 15%. So the water's sheen, rim and ambient light don't light the inside like the hide, and the open mouth is black under the fog. The earlier 64,365-byte version was checked against the posed mesh every 5 ms, with minimum jaw and whole-body clearances of 2.08 m and 0.61 m respectively. Those are historical full-mesh measurements, not new measurements of the fitted path.

The current predator path is fitted to the original rounded release's fixed chase, rather than simulated at startup. It has 33 adaptive cubic Hermite knots: positions in 1/128 m as signed 16-bit integers, velocities in 1/4 m/s as signed bytes, and 32 span lengths in 60 Hz ticks as unsigned bytes. The tables occupy 329 raw bytes. pred_build converts each segment's tangents to the existing Catmull-Rom evaluator and reconstructs the same 60 Hz path table that the rendering and seabed placement use.

A knot at story second 64 preserves the near miss's timing. The fitted release path deviates by at most about 6 cm at its centre and 1.23 degrees in heading from the old rounded release; banking can differ by about 5.9 degrees. This preserves the stalk, lunge, miss, chase and break-off, with no steering or collision-push solver in the shipped path builder. Clearance checks sample three spheres along the body at 256 Hz, and the placement shader keeps props away from the reconstructed path. The camera blow, cold desaturated darkening and vignette retain their original cues. The fit is an approximation of the release animation, not a claim of identical frames or full-mesh clearance.

4.3.10. Weed and kelp

algae.vert / algae.frag draw the seaweed, a two-sided ribbon of 16 segments that tapers to a narrow tip, sways in the current (more at the tip) and runs from dark to bright green. The ribbon is built in algae.vert from gl_VertexID, and the mesh is only a count: the 32 triangles of a 17x2 strip, with triangle t on vertices t, t + 1 and t + 2. The spine sways through the instance matrix. The tapering width is laid across it in world space, square to the eye, turning about the spine like a cylindrical billboard, with the normal horizontal across the face. So a blade is never seen edge-on, where it would thin into a sub-pixel stick that crawls under the jitter. A blade 0.24 m wide at its root is about 4 px wide at 40 m. Each blade is scaled by its instance. Blades sprout from the floor at staggered times and ease up to full height, so more weed turns up as the journey goes on. A wrap-lit subsurface glow and a wet sheen come up with uReal.

A tall blade (from 3-5 m up, by its instance's height) is a kelp frond, up to about 0.9 m across its middle, with a rippling edge. It sways three times as far and is tinted golden brown, and algae.frag's translucency takes the tint too. It still turns to the eye about its spine. The thickets' kelp is 6-11 m tall and some cells double their loose blades to 3.2-6.4 m, rising toward the surface. The placement tests each blade over its height and sway, which keeps the kelp clear of the camera and of the blob. Kelp over 3 m casts into the sun's shadow map through shadowalgae, which is algae.vert with shadow.frag.

4.3.11. Coral heads

coral.vert / coral.frag draw coral in four forms, built in coral.vert from gl_VertexID. The placement picks the form for each head (aColor.a, about a quarter each). The staghorn is a short trunk forking four ways, and each fork forks twice more, which makes 29 tapered six-sided tubes of 36 vertices, 1,044 in all. Each child starts at its parent's tip with the tip's radius, so the joints overlap. The other three forms are one surface of revolution built from the same 174 quads, 16 sides round by 10 rings. It's an ellipsoid with its lower half pulled down onto the sand, by the sixth power of how far below the middle a ring lies, and two waves round its rim to make it irregular. From it come a brain coral's half-sunk dome, a table coral's plate (a tenth of its size thick) on the cone of its stalk, and a sea fan's thin upright disc. All four stay inside the unit reach the placement gives a head, so every clearance holds, and all cast through the one shadow program (shadowcoral). A staghorn alone would read as a sprig at flight speed.

Each head is turned and scaled by its instance and carries its own shade in its colour, as rock.vert's foot tint does for the boulders. Whatever faces down, like a table's underside and stalk or a dome's foot, falls to 0.3 of the colour. The whole head darkens toward the sand, to 0.4 at its foot, and the heads vary in lightness by a factor of 0.7-1.2. A head's hue is one of seven saturated ones: pink, orange, purple, yellow, teal, red and rose (the water takes red first). On a table the hue is muted to 45% and the rest is the table's grey, warmed, which gives olive, ochre, dusty purple and rose. A table's growing rim is paler. On a dome the hue is muted a little, to 80%, and on a fan not at all.

coral.frag lightens the upper parts by the height up the head, which vColor.a carries along with the form. On the shared PBR model it adds a polyp speckle (0.75-1.25 of the albedo, a dotted top up close), detail normals and a subsurface glow. It cuts a fan into lace where a 3D noise strays from its middle, and ridges a dome with meanders, the bands of a sine of a noise. The pigments also fluoresce faintly in the blue light, at 0.15 uQ of the albedo squared, so the reef keeps its colour further into the water. Much more than that, and the tables become flat, evenly lit neon discs. Heads grow in from a point as the blob flees into the richer water. They add about 1-2 ms a frame at 150% on the UHD 620 through the flight.

4.3.12. The jelly (blob.frag)

The blob is Scene 1's (blob.vert / blob.frag, see The blue blob), and as the renderer grows up it turns into jelly. That's driven by uQ, the act's renderer-evolution ramp, which runs from 0 at the breach to 1 at story second 30 (69.7-95.3 s; see The renderer evolves). Once uQ passes 0.1, at 74.6 s, the blob is drawn last, after the seabed, the water backdrop and the moon jellies. Just before it, the demo copies the picture drawn so far into a texture (jelly_copy, texture unit 9), the whole frame in one glCopyTexSubImage2D. A rectangle round the ball wouldn't save much, because the lens reads up to about 2.3 radii from the ball's centre, so the rectangle would be most of the frame anyway. The copy takes about 0.3 ms on the UHD 620 at 1536x864, and next to nothing on a compo machine.

The jelly is a clear ball lens. Each colour's ray is bent into the ball, crosses its chord (2 R cos θ) and is bent out again. The exit normal is the entry normal reflected about the ray, so the shader needs no centre. The copy is read 1.2 R beyond the exit point, so the reef behind shows through magnified and upright. Along that chord the blue body absorbs red and green by Beer-Lambert. A ray straight through the middle keeps e^(-1.2 (1 - c)) of each channel, where c is the body's colour, (0.15, 0.40, 1). Only a tenth of the milky shading remains. The ball's relative index is 0.88 (an index of 1.136 against the water), and each colour's index differs from the next by 0.004 times the Fresnel term. So the colours split a little toward the rim and not at all face-on. Split across the whole face, they would break each magnified caustic line into red, green and blue contours, like an oil slick. A Fresnel reflection mirrors the water around it, bright above and foggy below, with the sky contribution scaled by 2.6. The sun makes a broader, dimmer glint (pow(..., 300) * 4), so small changes in the skin's normal do not make it flicker as sharply. The caustic web ripples over the top of the ball.

While the blob is lumpy (uWobble 1, until the transformation rounds it out over 129.1-139.7 s), each lump is a small lens of its own. At full strength the lumps would focus the seabed's caustic lines into concentric blue rings across the body, like thumbprints. So while it's lumpy, its rays bend by a normal halfway between the surface's and the view direction. Once it has rounded they bend by its own normal (0.93 of the way there at uWobble 0.15), and the seabed shows through as clear, warped glass. In the flight the lens shows the rushing seabed, magnified and changing every frame. Measured at about 150-152 s, it changes no faster than the seabed beside it (24 against 28 levels a frame at 30 fps).

The jelly's share J is smoothstep(0.1, 0.5, uQ) squared, so it's only a quarter at the halfway point. With an even blend, the dark seabed seen through the lumps would show as dark blue bruises in the half-milky body. Squared, the milky body holds until the jelly can show clearly through it, and the change still finishes on time: J reaches 1 when uQ reaches 0.5, at story second 15 (82.4 s).

Until the end of the transformation the face is turned to the camera. The sun's glint has its half-vector just above the point facing the view, so it would sit on or between the eyes, like a third eye or a white nose. So there's no glint within about 37° of that point, and it grades back in by 53°, scaled by how much of the eyes shows. render_scene2 works out how far the face is turned from the camera and sends the eyes' share in uGridFade, Scene 1's grid knob, which is unused under water. This way the glint also leaves the face when it glances back in the flight, and returns when the back of its head turns to us.

The blob writes depth with gl_FragDepth. As jelly it writes the depth of its centre. While it's milky (until 74.6 s) it writes the depth of a point half its radius in, because with the centre's depth the TAA would leave a ghost of its rim inside its edge. The bubbles, its eyes and the marine snow draw after it, so they aren't in the copy. That way the eyes, 0.9 of its radius out, show through its front half however the lumps lie, and the lens never smears ghosts of them across the body. While it's milky, trail bubbles and specks of snow within 1.2 radii of its centre aren't drawn, since they would show through it.

As a lens, the blob casts no shadow, but it focuses the sun. uwsurf.frag works out that light from the ball's centre and radius, which ride in the Frame block's uGlow and uGlowAmt, two fields Scene 2 has no other use for. Scene 1's object shader, which draws Scene 2's bubbles, eyes and marine snow, uses the same two fields for its glow at the origin. So the block is sent again with uGlowAmt 0 just before that draw. Otherwise the specks would be lit by a "glow" coloured by the ball's coordinates, negative channels and all.

Sunlight through a ball of index 1.136 converges about 4.2 radii behind its centre (f = n R / 2 (n - 1)). A soft-bodied ball with a wobbling skin focuses loosely, so the sand under it gets a soft Gaussian spot (1.2 radii across at 1/e²) of up to 2.65 times the sun's light, carrying the caustic web. It sits in a soft, dim penumbra (0.65 at its centre, back to 1 by 1.1 radii). It fades in below the ball, fades out again as the ball rises 3-7 radii above what it lights, and fades out beyond 20-45 m from the eye. This applies to the direct light on the seabed and the boulders, and fades in with uQ like the shadows. At 1.5 times the sun's light, the spot wouldn't show over the web and the sky's light. The fish, coral, weed and whale don't get it, but they rarely pass under the ball.

4.3.13. The blob's face

The blob has no face through the journey and the whole encounter, so the stalk, the near miss, the bolt and the chase all play on its body alone. Scene 1's eyes (blob_eyes, 0.9 of its radius out) only grow back once it has survived the scare. They grow from nothing at TRANS_IN (129.1 s) to Scene 1's size over 9 story seconds (to 137.4 s), wide open, while the camera circles the blob.

The face is turned by an angle with no roll. Until the flight it turns toward the camera, tilted to the camera's height, and the hand-over turns it the short way round. In the flight the angle comes from the direction to the camera alone. It never comes from the blob's heading, which weaves faster than the chase camera, so a turn measured from it would whip round as the heading crossed the camera's line. The face looks away, toward the side the chase's sway swings the camera out to, and as the sway peaks it glances back to a tenth of that angle, about 18° off the camera. The eyes shrink away while the face is 23-50° from the camera. The camera is close, so the outline it sees lies about 75° round from it, and the eyes sit about 25° out from the middle of the face. Every appearance and disappearance takes 0.27-0.33 s, and while the eyes show, the face never turns faster than 3° a frame at 60 fps.

4.3.14. Bubbles and marine snow

The bubbles are more instanced balls: the 14 rising out of the top of the blob and the dive's 40. The blob's bubbles are offset -1 to 1 along its heading, so they rise out of it and don't trail back between it and the lens. They carry alpha -4 (see Lighting (object.frag) for the codes), and object.frag shades them as air in water. Past the critical angle, where the view meets the skin at a sine over 0.75, the water's light is totally reflected. So the rim, graded in from a sine of 0.6 to full at 0.8, is a silvery mirror of the bright water around it: 3 times the fog's colour below, 7 times the light from above. With that grading, a bubble only a few pixels across still shows. Inside, they're clear apart from a sheen that keeps 20%, so the picture behind shows through, and the sun glints off them. render_scene2 draws the ball batch blended and premultiplied, after the jelly, so the jelly shows through the bubbles too. Everything else in the batch has alpha 1, so the eyes and the snow stay opaque. The dive's bubbles are 0.7-1.7 cm across per metre of distance from the lens, so they stay a few pixels wide as they rise past it.

The marine snow is instanced balls too: 500 specks of silt (5-18 mm) in a dim grey-teal with a touch of emission. They hang still in the water in a 10 m box that wraps round the eye. A speck shrinks away within 1.5 m of the lens (a smoothstep over 0.4-1.5 m on its size), since one passing 0.2 m from the lens would fly by as a white egg 120 px across. The backdrop has specks of its own, but they're fixed to view directions on the far plane, so they only show against open water and never move against anything. The snow hangs in front of the seabed, the rocks and the blob. It slides past with parallax as the camera trails the blob, streaks under the motion blur in the flight and goes soft in the depth of field. It's hard to see in a still and clear in motion. The specks take their own seed. The dive's burst draws from the bubbles' stream only while it lasts, so sharing that stream would move every speck when the burst ends. The 14 + 40 + 6 (two eyes, their lids and catchlights) + 500 instances stay within the ball mesh's 694.

4.3.15. Moon jellies (jelly.vert / jelly.frag)

Between the whale and the predator, at story seconds 40-55 (103.7-116.2 s), the blob would otherwise cross nothing but sand and boulders, in the same framing. So a swarm of 56 moon jellies drifts in the light above its path. jelly.vert builds each one from gl_VertexID. There's a bell of 16x6 cells on a flattened dome, with its rim drawing in and out about every 1.6 story seconds, and there are 12 strands of 10 segments, turned to face the eye. Eight are fine tentacles from the rim and four are broad, frilled oral arms from the middle, and all of them sway. Each jelly is tilted its own way, by up to about 15°, and its bell is 0.9-1.6 m in radius. jelly.vert places it by gl_InstanceID, 4-12 m to one side of the point on the blob's path (in uwplace.comp's buffer) that the blob passes at that jelly's own moment.

jelly.frag adds it over the water, on both sides and writing no depth, from story second 36 to 57 (100.4-117.7 s), fading in and out over the first and the last 3. It draws the clear tissue, which shows where it's seen edge-on, the rim and four pink horseshoe gonads round the crown. They're lit by the sunlight left at the jelly's depth and dimmed by the water's extinction with distance. The light the water scatters is already drawn behind it.

Eight cyan radial canals run out across each bell, with faint light on the trailing strands. Their glow pulses slowly with a phase that travels outward, and fwidth softens the fine lines at the pixel scale. That derivative is evaluated before the bell/strand branch so its footprint remains valid. The glow rises over story seconds 38-43, holds through 51 and fades by 56. Over the same envelope, the water's scattered-light colour dims by up to 30% and exposure drops by 0.10, so the canals show against a quieter background. This uses the existing jelly draw and bloom; the 56 jellies keep their geometry, paths and clearance.

The jellies' height keeps them clear of everything else whatever the hash gives, which matters because sines differ between GPUs, so the layout may too. At story second 48 the bell's centre is 3.9-5.9 m up, and it rises 0.1 m a second. That keeps its crown 0.8 m under the surface and the tips of its arms at least 1 m above the camera, the blob and the schools' fish. None of those rises above -0.8 m from story second 34 to 58. We measured every anchor, side, distance and height the shader can pick. One of the load's warm-up frames (half bar 105, at 101.6 s) draws the jellies, so their program is built during the load.

4.3.16. The seabed and its props (uwplace.comp)

The seabed is a large, finely subdivided heightfield (mc_mesh_terrain's, computed on the GPU) sunk well below the blob. The boulders are a dense ball displaced by three layers of noise into craggy rock. Both are computed once at load by the shader that then places the props.

Every boulder, weed blade, coral head and crab in the act is placed once at load by a compute shader, uwplace.comp. It works against one buffer that holds everything the props must keep clear of. That's the blob's whole path (which is deterministic), the picture's camera (camera_uw) every quarter of a story second, the predator's path, the fish schools and the seabed's heights. A candidate's id says where it may stand. The 240 m seabed is split into 34x34 cells with 48 slots each. The current recipe uses two slots for fill boulders, six for weed and three for coral around a shared clump. Slots 11-33 scatter a mix of loose boulders, coral, crabs and blades, using the four LOOSE ranges for their sizes and seed times. Slots 34-47 form late coral patches with one hue per cell. The recipe shares its placement and sizing code instead of giving each cell separate boulder formations, kelp groves, two-hue patches and age-dependent size variations. The scene's larger structures still come from 18 landmarks of 96 slots each: six rock outcrops, six reefs and six kelp thickets. A landmark reef uses 56-85 of its slots, three quarters of them coral heads ringed by kelp.

The shader runs in 21 passes. The first works out the sand and the boulder. The sand is the 201x201 heights of mc_mesh_terrain's fBm, with the engine's noise ported exactly. The boulder is a cube's six grids (20, 10 and 5 cells a side for its three LODs), pushed out by three layers of that noise, and each vertex gets the normal of the surface around it. Both stay in the buffer for the frames. object.vert builds the seabed from the heights and rock.vert builds the boulders from their vertices, so each mesh is only the indices of its grid.

The next pass sites the landmarks beside the flight one after another, each with room for an outcrop's core and each born unseen once the predator has gone. Each site tries up to 99 points.

One pass seats every candidate on the sand by replaying the seabed's triangles. It keeps a candidate if it clears the blob's path and the fish schools, including the big school's whole disc, 12.5 m round it and 3.5 m above and below. The middle's props must also keep out of the whale's lane. The fill is seeded from about story second 60, long after the whale has gone. A boulder must also clear the camera's and the predator's paths, and its radius leaves headroom for rock.vert's growth. A blade is tested over its height and sway, just as coral heads and boulders are tested as they stand, so a short blade may stand under the path.

Sixteen passes settle conflicts the same way placing the props one by one would. The middle's props come before the fill, boulders before coral, crabs and weed, and after that the lower id wins. The decisions are double-buffered, and they settle by about the tenth pass. The one exception is a head in a coral patch. It makes way for the middle's boulders, coral, crabs and weed, which are all there before it, though a blade may stand at its edge, at 0.9 of its reach. It also packs tighter itself, overlapping the other heads of its patch (0.2 of the two reaches, against 0.32 between other coral heads). So a patch forms one mound, and nothing that stood there before it has to go.

One pass sets the timing of every prop that's kept. Each prop grows in from the first moment after its seed time at which, for the whole grow-in, the camera stays more than 70 m away or keeps the prop out of a 60° cone (born()). The range is that long because the water clears early, and at a shorter range a boulder far off could be seen swelling up from a dot. A prop dies when a landmark grows over its spot. One that's never out of sight for that long grows in at its seed time if that's before story second 50, and is dropped if it's later.

A boulder seeded before story second 40 may be born from the start of the act. Without that, at 70 m they'd all be born late, and under the whale's pass the lower two-thirds of the frame would be bare sand. Over the first 3 story seconds, in the murk, out of sight means further than 25 m, since at clarity 0 the water passes only about a tenth of the light from 30 m away. A boulder born then has its birth set 3 s earlier, so it stands fully grown from the first frame and never visibly grows. That puts 9-11 boulders in frame within 60 m over 74-96 s. The price is a sparser early act, since some props come later.

The last pass works on 68x68 squares of the sand, 3.5 m each (half a cell). For each square it lists the kept boulders whose contact shade reaches into it, up to 16. They're stored as the places of their records in the buffer, which stays bound on binding 0 for the frames. The squares are half a cell wide to keep the lists short, since the sand reads its square's list at every pixel. The pass also works out the reef bed at the square's centre. That's how much bed is laid down by the heads of the coral patches, the landmarks' coral and every head born after story second 80, leaving out any head a landmark grows over. Each head lays down a disc that fades out from half its reach to twice it, wider the later the head is born (up to about 4.25 times for the last). And the pass works out when the bed may grow in. That's the first moment after the last of those heads is born at which the square, too, stays out of sight for the whole grow-in, by the props' own rule. If that never happens the square gets no bed, which is the case for a handful of squares.

The CPU reads the result back into one list of props, which uw_prepare draws in one loop (growth, death, culling and the boulders' LOD). The 25 circling fish schools stay on the CPU, and it's the props that keep clear of them. A 26th record holds the big school, which fish.vert places. All this takes about 155 ms of GPU time at load in the earlier 64,365-byte version, about 60 of it for the lists, since every square scans every record. That version held 4,337 props. The current integrated verification retains 4,916: 636 boulders, 725 coral heads, 277 crabs and 3,278 weed blades. It checks all 4,050 late growth intervals and the path, camera, fish, predator and boulder clearances. The revised population recipe changes the layout. Its 21 passes retain the path-clearance tests, ranked conflict resolution, offscreen grow-in search, landmark death timing, contact-shade lists and reef-bed generation. That version's clearance measurements and load timings should not be treated as measurements of this revised layout.

The camera's path runs through the ring of one circling school, 3.35 m round, coming within 3.5 m of its centre at 111.8 s. That school keeps its ring, so the props still keep clear of it, but it's drawn empty, or one of its fish would cross the lens. A school shows no fish if any of the camera's samples comes inside a circle a metre wider than its ring, and only that one does.

The fill is seeded from about story second 60 and the landmarks are born once the predator has gone, so the reveal brings in the biggest structures last. Meanwhile the counts of visible rocks, fish, crabs, weed and coral keep climbing right through the closing flight. Props are seeded up to story second 96 and the circling schools fill up until 98 (148.5-150.0 s). The blob speeds up and spirals outward too: blob_phase, plus a widening of its wander during the flight, makes it really go further and faster. So the picture gets more detailed and more crowded with life as the blob travels, and it ends on a fully populated reef that shows what the engine can do.

4.4. The renderer evolves (the uQ ramp)

Three ramps on the story clock change the act's look as it runs. render_scene2 works them out from uw_u() and hands them to every underwater shader in the Frame block. The water's clarity (uClarity) is smoothstep(0, 9, u). It takes the fog and the light from murk to clear over the first 9 story seconds, and it's done by 77.3 s. The realism push, real (uReal), is smoothstep(8, 48, u) and runs over 76.5-110.4 s, finishing as the encounter draws near. It layers finer detail textures and relief onto the surfaces, sharpens their speculars and brings in the fish's baked skin. The third, uQ, is smoothstep(0, 30, u) and runs over 69.7-95.3 s.

uQ changes the rendering model itself. It feeds every underwater shader and the post stack, and it blends a flat, cheap early look into a full modern one. It also turns the blob to jelly (J = smoothstep(0.1, 0.5, uQ), squared), so the jelly is fully clear from 82.4 s. The shadows, the occlusion and the PBR are nearly all in by about 90 s (uQ 0.9) and fully in at 95.3 s. So the renderer visibly grows up over the act's first 26 s, from murk to clear and from flat to lit. By the finale the engine is doing all of this at once:

  • Physically based shading. Every underwater surface (the seabed, boulders, fish, crabs, predator, whale, weed and coral) runs the same Cook-Torrance microfacet BRDF: a GGX normal distribution, a Smith geometry term and Fresnel-Schlick reflectance, over a per-material albedo, roughness and F0. It conserves energy, so the speculars tighten and the ground looks like a real material instead of ad-hoc Lambert.
  • Analytic image-based lighting. There's no room for cubemaps, so the ambient light is an analytic underwater environment (uwEnv). It's bright toward the surface, and below it's the sand's light bounced back up, (0.04, 0.11, 0.14). So over bright, sunlit sand, anything facing down (a rock's flank, a fish's belly, the whale's side) is lifted instead of going black. The shaders sample it for diffuse irradiance, and along the reflection vector, blended by roughness, for a specular "probe", and mix the two by Fresnel. That's what makes the wet surfaces look lit by the water instead of a lamp.
  • Detail normals. A procedural height field, applied without tangents through screen-space derivatives, adds micro-relief that grows with uQ: pores and grain the mesh never had.
  • Wavelength-selective extinction. The water takes red first, then green, like Jerlov's oceanic water types. Extinction is per channel and goes from murky (0.18, 0.07, 0.05 per metre) to clear (0.06, 0.018, 0.013) as the act clears. It's the cue that most clearly says you're under water. The light scattered in along a ray is integrated in closed form (waterFog) and gets brighter as the ray climbs toward the light. The fog's colour is set by the camera's own depth.
  • Caustics and shafts from one field. The light the surface's ripples focus is the caustic web, read where the sun's ray through a point left the surface. On the seabed, rocks, creatures and coral it makes the dappling, and red's lines are widest, so the edges fringe. On the jelly it's a faint shimmer, and in the backdrop it makes the shafts (see The water's light), so the shafts stand over the brightest dapples. They're the only shafts: there's no screen-space god-ray pass.
  • Ambient occlusion. The scene renders depth to a texture that can be sampled, and the engine's half-resolution GTAO pass darkens contact creases and the lee of every rock and fish (see The renderer and its post stack). Its strength is 0.7 uQ.
  • Jelly. The blob stops glowing like a lamp and becomes a clear ball lens that magnifies the reef behind it and focuses the sun on the sand (see The jelly).
  • Sun shadow mapping. A depth-only framebuffer and a position-only pass render the big casters into a 2048² depth texture, from an orthographic sun view fitted round the first 40 m of the camera's view. The casters are the boulders, the coral, the kelp, the whale and the predator. The jelly isn't one, because as a lens it focuses the sun instead. The seabed casts nothing either, since its own shadow would dim the caustics the look is tuned with. The shadow pass shares the prepared instance ranges with the colour pass, and the rocks, kelp, whale and predator deform their vertices in it just as they do in the colour pass. Culling is done separately against the camera's and the light's frusta, with conservative bounds for the animation and three rock LODs with hysteresis. Every lit underwater shader then projects its world position into the light's space and takes 3x3 taps of the map (shadowVis), so surfaces fall into shadow under the rocks, the kelp and the creatures. The hardware does the depth compare: the map is a texture in compare mode, linearly filtered and read through a sampler2DShadow. So each tap is itself a bilinear 2x2 PCF, and a penumbra grades smoothly instead of in ninths for the same nine fetches. The shadows fade in with uQ, and out between 35 and 40 m from the eye. Scene 1 and the end card never share a frame with Scene 2, so they borrow the map for their key light (see Lighting (object.frag)). Scene 2 draws the map afresh before it reads it, on every frame the sea below is drawn, from the crossing's first frame on and with uQ at 0 too. That's because object.frag's keyShadow reads the map whatever uQ says. Without the pass, it would compare the bubbles, the dive's burst and the marine snow against Scene 1's last key-light depths.

The early seconds use almost none of this (uQ near 0 gives a flat, murky look), and the finale uses all of it (uQ near 1). The geometry stays the same, and it's rendered as if the engine grew up over the act. The PBR, IBL, detail normals, extinction and shadow sampling live in the same functions at the top of uwsurf.frag, fish.frag, whale.frag, algae.frag and coral.frag, and the water's light lives in a second, smaller block (below). The ambient occlusion is a post pass in render.c, gated by mc_post.ssao, so it costs nothing in Scene 1. The shadow map is a pass the demo draws between mc_renderer_begin and the lit draws, and it hands the framebuffer back with mc_renderer_bind_scene.

4.5. The water's light

Eight shaders share one block of code, byte for byte, between two marker comments (// ---- the water … // ---- end water ----), so the water lights everything in it the same way. The eight are the backdrop (water.frag), the seabed and rocks (uwsurf.frag), the fish, the whale, the coral, the weed, the blob and the moon jellies. The block holds the surface's height (8.5 m up, and the camera stays under it), the water's extinction (wx, red first, murky to clear over the act), the caustic web (cw) and the water's phase function (wp):

  • The caustic web is the squared distance to the nearest point of two or three rotated, drifting lattices. Each lattice is the previous one turned and scaled by one fixed matrix, with time as the third coordinate. The web is read at the point where the sun's ray through the shaded point left the surface. A lit surface takes it to the 4th, 5th and 6th power of that distance for red, green and blue, each normalised to a mean of 1, so red's lines are the widest and fringe. The web fades to 1 where the pixel's footprint spans the lines, so far rocks skip it, and it gets stronger as the water clears. The weed leaves it out, since on thin blades it can't be seen and still costs time.
  • The fog (waterFog, in the five surface shaders) is the sunlight left at the point's depth times the transmission along the ray, plus the light scattered in along the ray, in closed form. The sunlight falls off at half the extinction per metre of depth and is 1 at the blob's depth, 13 m down. The in-scatter is uFogColor × c/e × (1 - e^(-e s)), with e = c - 0.5 c d_y, so it brightens toward the light. The C side scales uFogColor to the eye's own depth with the same formula.
  • The phase function wp scatters forward, half evenly and half by Schlick's Henyey-Greenstein with g 0.4. It's 1 across the light, about 3.4 toward the sun and 0.7 away from it, in the fog and the backdrop alike. That's why the total-internal-reflection mirror, seen looking down and away from the light, is dark.

The sun comes down 29° off the vertical, along (0.5, 1, 0.25), well inside the 48.6° an underwater sun can reach. The light and the shadow map share that direction. At that slant the boulders, coral, predator and whale throw shadows beside them, and the rocks sit on the sand instead of looking pasted on it. The backdrop's shafts keep their own, lower slant. They're extruded along 1.2 times the sun's slant, 34° off the vertical, because shafts show best when the eye looks along them.

The clear water's colour is (0.04, 0.22, 0.37), a deep ocean blue. Every ray saturates to it, so it's also the colour of the backdrop at the horizon and of everything distant. The water absorbs red first and blue last, so the near sand stays a pale cyan-white while the distance goes blue, which gives a change of hue from near to far. A teal of the same brightness would pull the sand, the rocks and the jelly toward it, and the act would come out nearly monochrome cyan. It's a grading choice.

At story second 26 (UW_LOOK, 91.8 s) the camera glances up at the whale passing overhead (uw_look, shared by the camera and the post). The glance rises over story seconds 21-26 and holds through 28. The camera eases 10 m back along its direction from the blob, rising a little as it goes. At the held view the blob is about 28.2 m away rather than 18.2 m, so the mother and calf have room above it. The view turns 40% of the way from the whale to the blob, and a little up, and the lens tightens from 55° to 48°. So the whale stands against the bright water overhead, with the blob in the foreground. The exposure eases down 0.35 for the shot. The view stops just under Snell's window, because the window's broken rim would flicker in along the top of the frame as lime-green chips. Then, over story seconds 28-32, the trailing camera takes over again, since a faster swing down flashes the window's bright rim across the frame. The props are placed against this camera, glance and all.

The backdrop's shafts are sharp sheets, gathered from six slices along each ray. Swept through at speed, they would strobe. When the eye trembles (the blob's panic, about 0.3 m at about 10 Hz) or flies and turns fast (16-65 m/s, up to 123°/s in the flight), each slice would jump from sheet to sheet between frames. The specks fixed to view directions would sweep across the frame in jumps, and so would the bright rim of Snell's window as the eye comes back down from the glance. So each frame main.c traces the eye's path over the half second around the current time (31 points of camera_uw, about 0.04 ms). It hands water.frag two things in uP (location 0). One is the path's mean position, which the shafts hang from, so the tremble doesn't shake them. The other is a calm value. It's 1 while the eye moves less than about 13 m/s and turns less than about 50°/s, and it falls as the eye goes faster, sooner near the surface where the sheets are sharpest. As it falls, the sharp sheets give way to the same web four times broader and softer, and the specks fade, as do the slices nearer the lens than 2 m. The window's rim, facets and glint calm down the same way, as a shutter would smear them. Calm is 1 from the breach to the start of the encounter (69.7-116.7 s), apart from about 2 s at 94.6-96.7 s, as the blob turns away from the whale and the camera swings after it.

What still jumps in the panic is its tremble shaking the whole frame, which is intended. The seabed and the backdrop meet without a seam, because the backdrop's in-scatter is the same formula as waterFog.

5. Scene 3: the jungle

Scene 2 burns out to white. Scene 3 (demos/hello-world/scene3.c, which main.c #include=s) holds the white for one bar, opens out of it onto the floor of a rainforest, and ends on a beach. It fills 154.8-269.0 s of the show, the 59 bars of the song's fourth movement. Its cues are written in /jungle seconds/, counted from the end of the white (=S3_J0), and each sits on a bar line of a story 54 bars long. The show plays that story over 58 bars, because in the forest the look round, the canopy, the ladybug and the glide each last a bar longer. Once the beach is in sight, show time is simply 164.516 s plus the jungle time (S3_J0 and jungle seconds).

The table gives each beat in show time, with its span in jungle seconds and the cue that starts it. The times inside the rows are show time too.

Beat Show time Jungle time On screen
White 154.8-156.8 s before 0 Pure white. From 156.2 s, half a jungle second before it lifts, the jungle is drawn under it, unseen, ready for the reveal. The shaders and the still sun maps were made at load.
Reveal 156.8-159.5 s 0-2.4 s (S3_REVEAL) The white lifts first, while a 5x exposure overshoot and its bloom linger. The trunks rise as silhouettes in blazing light, like eyes adjusting as you step out into daylight.
Look round 156.8-172.3 s 0-13.5 s We stand on the forest floor. The gaze drifts left across the clearing (yaw -12° to -88°) and the eye eases a couple of metres forward. There are giant buttressed trunks hung with lianas, saplings and palms layered into a green haze, and sun shafts slanting through it. Two blue morphos flutter past near the eye, and a pair of macaws crosses far off between the trunks.
Canopy 172.3-181.9 s 13.5-21.3 s (S3_UP) The gaze climbs the trunks to the canopy (pitch up to 57°), where a gap stays open in the leaves. Four scarlet macaws cross it at about 12 m, and a flock of nine green parrots goes over higher up, the other way.
Descent 181.9-187.8 s 21.3-27.1 s (S3_FIND) The gaze comes down and round (yaw to -250°) to a clump of big elephant-ear leaves right beside us. From 183.9 s the macro rig takes over and the camera closes in.
Ladybug 187.8-203.2 s 27.1-40.6 s (S3_MACRO) We're 4.5 cm from a seven-spot ladybug. It walks along the hero leaf's midrib past dew drops, in a fleck of sun, with the background melted into bokeh. Halfway along it stops to look round while the camera slowly orbits it.
Take-off 203.2-207.1 s 40.6-44.5 s (S3_FLY) The wing cases lift and spread, and the hind wings unfold and blur into their beat. At 204.9 s it lifts off and flies up into the light. The camera stays put, widening and tilting up after it as the light swells.
Glide 207.1-214.8 s 44.5-50.3 s (S3_ROAM) The camera lets the ladybug go. It comes down from the canopy, turns out from under the leaves and glides between the trunks a metre or two off the ground, toward brighter light.
Glimpse 214.8-216.8 s 50.3-52.3 s (S3_GAP) The camera comes to rest among the trees at the vantage (S3_VANTAGE, 13.5 m out, about 21 m back from the tyrannosaurus's track). Two giant buttressed trunks 10.5 m apart frame a wide view of a beach: sand, surf, sea and sky. Tiny glimpses of the animal appear through the trunks as the music darkens.
Tyrannosaurus 216.8-224.5 s 52.3-60.0 s (S3_REX) Its footfalls land every two beats, and each one jolts the camera and briefly bows nearby ferns. We crouch into the ferns, reaching 0.48 m up and 1.6 m to the right at jungle 57 s, while the lens tightens to 29°. Real trunks break up the shoulders around jungle 53-54 s; the head emerges around 55 s and the body around 56 s. A dark, banded shape walks out of the crowns' shade into a pool of sun, with a rim of light along its back against the sky. From jungle 56.2 to 57.4 s it turns almost directly toward us. It opens its jaws with two snorts (222.4 and 223.1 s), and the first makes us recoil. Then it growls, four leaves flutter down from the right canopy, and it turns away over jungle 59-60.4 s.
Walking on 224.5-232.3 s 60.0-67.7 s (S3_SHORE) It walks on behind the right-hand trunk: the body is hidden around jungle 62 s, the tail follows around 64 s, and it is out of view by 66 s (230.5 s). We hold, crouched, through the four bars of S3_HOLD, and the look drifts back toward the gap. The dark drone, bass pulses and its footfalls (thirteen in all, fading as it goes) keep the forest quiet until the relief at jungle 67.7 s.
Stepping out 232.3-236.1 s 67.7-71.6 s (S3_CLEAR) The relief comes in and the forest wakes. The camera stands and steps out between the trunks toward the trees' last row, about 22 m out, which it reaches as the blob rises.
The blob 236.1-245.8 s 71.6-81.3 s (S3_BLOB) The blob rises out of the surf about 13 m out and shakes off the water. Then it hops up the beach, nine hops, one a beat, paddling its two tiny arms for balance. The camera sees it arrive in a wide shot from the trees' last row, where it's 2% of the frame high. As the blob hops toward us, the camera walks out of the trees onto the top of the beach (27.5 m out by 242.3 s), and the lens lengthens from 54° through 32° toward 20°. It's one continuous dolly and zoom, with no cut. The blob settles on the wet sand (about 242.4 s), blinks and looks round.
The wave 245.8-249.7 s 81.3-85.2 s (42 bars in) It raises its arms and waves at us from about 15 m away. The lens tightens from 20° to 18° and the eye is 1 m over the sand, looking slightly up at it. The blob is about a fifth of the frame high, so its arms and eyes are easy to see. It sits in the lower third against the surf, with the sky over it.
The play 249.7-261.3 s 85.2-96.8 s (44 bars in) Two hops turn it round to face the sea while a bigger wave's swash runs up toward it. It hops down after the draining water (251.6 s), landing the last hop in it with a splash, and bounces as the next wave comes. At 256.9 s it jumps over that wave's foaming front as the wave runs up under it, a jump two beats long and 35 cm high. It lands in the water at 257.7 s in a burst of spray. The spray throws a straw sun hat, a red and white beach parasol and a folded deckchair up out of the water. The blob catches them: the hat lands on its head, the parasol in its right hand (it springs open over the blob at 258.1-258.6 s) and the chair in its left. It stamps in the water, splashing, and turns back as the water drains. The camera drifts 4.5 m sideways along the top of the beach, 16-17 m from the blob and 1 m over the sand. The sun comes round beyond the blob and lights the spray from behind. The lens tightens to 17° for the catch, and the blob stays about a fifth of the frame high.
Watching 261.3-267.0 s 96.8-102.5 s (50 bars in) It hops back up the beach with its things, out of the water for the last time, and shivers itself dry on the sand (262.8 s). Nothing flies off it there, since only landings in the water throw spray. It turns half round, takes a step down toward the sea and sits under its parasol to watch it. The swash of the next, bigger wave laps round it (about 265.2 s) and runs 30 cm past it as it lands a last bounce: a splash, both arms up, a contented tilt. From 264.8 s the camera goes back among the trees, rising from 1.75 to 3.25 m and widening to 54° for the last wide view.
Fade 267.0-269.0 s 102.5-104.5 s (S3_OUT) The scene fades to black on the blob, and the end card follows.

5.1. How it's put together

scene3.c holds what everything in the scene shares:

  • the timeline: S3_J0 and the cues (below)
  • the floor's height, s3_ground, which the GLSL mirrors as s3h()
  • the tree sites (The trees)
  • the window of sky that the canopy keeps open, for the birds to cross (S3_GAP_YAW, S3_GAP_PITCH, S3_GAP_COS)
  • the beach's frame of reference
  • the camera (The camera)
  • the per-frame context S3Ctx, and render_scene3, which draws a frame

It then #include=s the eight package files ([[#the-s3-packages][The packages]]), so =main.c, scene3.c and the packages are one translation unit. The packages' shaders share one block of GLSL (The s3 common block). The Frame block's fields, the sun maps and the texture units are listed in Frame fields, sun maps and texture units.

The beach lies at yaw 60°, a direction the look round never turns toward, so the beach can't be glimpsed early. s3_along(x, z) is the distance toward the sea, s3_across(x, z) the distance to the right as you look out, and s3_beach_pt converts back. The forest stops at S3_EDGE (32 m out). Past it, s3_ground blends the rolling forest floor into a beach that slopes 6% down through the waterline (S3_SHORE_S, 50 m, with sea level S3_SEA_Y at -1 m) and on under the sea. s3h() has the same blend, so everything placed on the CPU and grown on the GPU sits on the same sand. The glide stops at S3_VANTAGE (13.5 m). The tyrannosaurus walks along S3_REX_S (34.5 m) and puts a foot down every S3_STEP (two beats) from S3_REX, and the blob comes ashore at S3_BLOB_ACROSS (1.5 m to the right).

S3Ctx is everything a package needs to draw a frame. It holds the scene's time and the jungle time, the macro amount (0 wide to 1 at the ladybug), and the beach part's progress (0 in the forest to 1 from S3_GAP). It also holds the focus distance, the camera with its view-projection and frustum, and the pass being drawn. s3_setup fills it in for any moment. The sun maps, the trees' detail and the impostors' bake use it to look at the world at moments other than the frame's own.

5.1.1. S3_J0 and jungle seconds

render_scene3 gets the scene's own seconds, 0 at SCENE3_START (154.8 s). The first bar is white. S3_J0 is the length of that bar, one BAR_S (1.935 s), and jungle seconds count from its end: c->j is the scene's time minus S3_J0, negative in the white. Every cue is a jungle time on a bar line, written as a whole number of bars: S3_UP is 7 bars, S3_REX is 27 and S3_END is 54. The shaders that time their own action count the same jungle seconds, from uTime and the common block's s3j0.

The clock is stretched once, at the top of render_scene3. The scene's seconds go through stretch() with S3_SEG and come out as the story's, which is all that the camera, the packages and the shaders ever see. S3_SEG pairs bars of the show with bars of the story:

Part Show bars Story bars
look round 8 7
canopy 5 4
way down 3 3
ladybug 8 7
take-off 2 2
glide 4 3
from the hush on 1 1

The story ends up four bars behind the show, and from the beach's hush on it runs at the show's pace. Each change of pace is eased over half a bar. So from a quarter bar after S3_GAP on, show time is SCENE3_START + S3_J0 + 4 bars + j. That is 154.8 + 1.9 + 7.7 s + j, or 164.516 s + j. The Timing section explains stretch() and how the clocks keep to the music, and Scene 3's music lists the bars each beat falls on.

S3_HOLD is four bars of the story in which we stay crouched while the tyrannosaurus walks out of the picture. Every cue from S3_CLEAR on has it added, in scene3.c and in jo_blob.vert, jo_blob.frag, ju_ground.frag and the surf block. The shaders carry it as a number (7.74 s, or a time that includes it), so a change to S3_HOLD has to be made in all of them.

5.1.2. The packages

The work is split into eight packages. Each is one C file that scene3.c =#include=s, with one shader prefix and a manifest of its own programs:

Package Shaders What it draws
s3_trees.c jt.*, jt_bake.frag the giant trees, their crowns, palms, saplings and lianas, the far forest, and the baked bark, leaf and sand tiles
s3_under.c ju_* the forest floor and the undergrowth
s3_light.c jl_* the sun, the atmospheric sky and wispy clouds, the sun maps, light shafts and the grade
s3_bug.c jb_* the hero plant and the ladybug
s3_birds.c jv_bird.* the birds and the butterflies
s3_rex.c jr.* the tyrannosaurus (jr.comp is the creature kit, which sculpts it along with Scene 2's animals)
s3_blob.c jo_blob.* the blob, come ashore, and what it carries
s3_beach.c jw_sea.* the sea and its sky reflection (the sand is the floor's, in ju_ground.frag)

Each package has make, draw and free hooks, except the ladybug's, which has only draw. s3_make places the trees and works out their detail, then runs the make hooks at load, and s3_free runs the free hooks. draw is called once per pass, with c->pass set to one of four values:

  • S3_SHADOW, the still sun maps. uViewProj is uLightVP and uQ is 1, so shaders only do their cut-outs.
  • S3_LIT, the picture.
  • S3_CHAR, the characters' own sun map, drawn every frame. The ladybug, the tyrannosaurus and the blob draw their depth there the same way as in S3_SHADOW.
  • S3_LATE, after the sky, for whatever has to look through the finished picture.

A frame runs in this order. light_frame writes the lighting terms into the Frame block. light_shadow draws the sky's radiance table, then the still sun maps if they haven't been drawn yet (a dev build redraws them every frame, so hot reloads show), then the characters' map. Then comes the lit world: the hero plant first (up close it hides most of the frame), then the undergrowth and the floor, the trees, the birds, the tyrannosaurus, what the blob carries, and last the sea, which is see-through. After that come light_sky and the late pass. The late pass draws the blob's jelly, which needs the sea and the sky behind it. The sea and wet sand reflect the sky in their own material shaders. light_volume adds the shafts, and light_post sets the grade and the depth of field for the engine's post chain.

Each package lists its programs in its own s3_<pkg>.def manifest. programs.def pulls in scene3.def, which pulls in the eight manifests, and the Demo struct gets one mc_shader field per entry from the same X-macro. So startup, hot reload, the link checks and the teardown all see the jungle's programs with no per-program code.

The jungle has no mesh files. Every trunk, leaf, blade, frond and bird is grown in a vertex shader from gl_VertexID and gl_InstanceID, and so are the floor, the sea and the blob. The ladybug's parts are squashed and folded copies of one small sphere, which jb_bug.vert also builds. The C side only decides where things stand and hands the shaders a uniform table. The tyrannosaurus is the exception: at load the creature kit sculpts it into a mesh on the GPU (The tyrannosaurus). The bark, leaf and sand tiles and the far forest's impostors are baked at load too (The trees).

5.1.3. The s3 common block

The s3 common block is a stretch of GLSL that the jungle's shaders carry byte for byte the same, between two marker comments:

// ---- s3 common: keep byte-identical in every jungle shader (owner: s3_light) ----
...
// ---- end s3 common ----

The jungle shaders share it wherever they need the scene's lighting and world helpers; the bake (jt_bake.frag) and creature kit (jr.comp) do not. The light package owns the block, and a change to it is made in one copy and copied over the others. The block keeps every package in the same light. The context-mixing packer codes the copies for almost nothing, and genshaders.py stores the block once (genshaders.py). It holds:

  • the Frame block, which every scene's shaders share (Scene 3's meanings for its fields)
  • the samplers for the sun maps (units 3-8) and the sky's radiance table (unit 15), bound with layout(binding), so no C code sets them
  • s3sea, the direction toward the sea, and s3j0, the white's length (S3_J0)
  • s3h(), the floor's height
  • s3shadow(), the sun's shadow at a point
  • s3lit() and the s3light() macro over it, the lighting
  • s3sky(), the open sky, and s3fog(), the air between the eye and a point

Shaders that time their own action count jungle seconds as j = uTime - s3j0, the same as scene3.c. They are the tyrannosaurus's walk and its prints, the blob's hops and trail, and the birds.

s3shadow() is a soft sun shadow whose filter widens with the distance to the blocker, so the canopy far overhead throws soft dapples and a leaf close by throws a sharp shadow. It reads the clearing's map, or the beach's map past the clearing, with four hardware-compared taps, each filtered over 2x2 texels. Beyond both maps the canopy is taken to let a fifth of the sun through, and the open beach and sea get all of it. That is multiplied by one tap of the characters' map, which lets uBlobFog of the sun through because the blob's jelly isn't solid. The characters' tap is offset by a turn hashed from the point, over a radius that grows with how far the point lies past the character's middle along the sun (uGape is that depth). So the shadow's edge blurs with distance, as light bent by the jelly spreads out.

s3light() is physically based. Its highlight is GGX with Smith's height-correlated visibility. Its diffuse is rough, using the energy-preserving Oren-Nayar model (EON, Portsmouth et al. 2024). That model is flatter than Lambert, brighter with the sun behind the eye, and adds the light that scatters more than once. The diffuse uses half of each material's roughness, so sand, moss and bark come to about 0.45. Going rougher would dim the sun's flecks on the trunks, which we see looking up toward the sun. On top of that come the yellow-green light that thin leaves let through from behind, the sky and bounce ambient, and an ambient sheen. The sheen is what the surface mirrors: under the canopy that's the dark trunks round the horizon and the lit leaves overhead, and on the beach it's the open sky. It comes from Karis's analytic fit of the pre-integrated GGX, times Lagarde's specular occlusion.

The terms conserve energy. The highlight and the sheen get back what single scattering loses on rough surfaces (Turquin's compensation, about 1 + 0.04 rough² for F0 0.04). The diffuse ambient gets what the sheen leaves, a factor of 1 - E. The ambient also takes the surface's own occlusion through Jimenez's coloured multi-bounce fit (s3am(c, alb, ao)), so bright sand and lichen turn less grey in their hollows. The screen-space occlusion doesn't know the albedo, so the share left to it is lightened the same way.

s3lit computes the shadow first, so that little else is live across its texture fetches. Computed later, it pushes programs out of Intel's SIMD16 mode. The lookup reads the sun's maps 3 cm off the surface on the sun's side, along the normal times the sign of N·L. The normal faces the eye, so without the sign a leaf, a feather or a wing seen from its shady side would read its own shadow and lose the light coming through it. With it, backlit ferns and low leaves glow yellow-green where the sun comes through them, and the macaws' underwings catch the light.

s3light() widens the roughness by the normal's screen-space variance, a form of specular anti-aliasing that keeps glints on far leaves from sparkling. Vertex shaders share the block but have no derivatives, so s3light is a macro over s3lit() that only a fragment shader expands. genshaders.py drops unused macros and functions from each shader, so the vertex shaders lose the lighting and whatever only it calls.

Inside the forest, s3fog() is a humid haze that thickens toward the ground. For the part of a ray that lies out over the beach and the sea, it is thin, bright sea air that scatters the sky's own light toward the eye. This gives aerial perspective: the far sea and the far edge of the forest take on the sky's haze, warmer toward the sun. s3sky(d) is that sky, the open sky's radiance by direction, read from a table that jl_atmo.frag draws every frame (Light). The sky pass draws it. The sea, the wet sand and the blob mirror it, and the sea air scatters it.

s3light() also keeps the ambient part of what it returns, and s3fog() turns that into s3a, the ambient's share of the final colour. Every jungle shader that lights a surface writes s3a in alpha, so the post's ambient occlusion darkens only that share and the contact shadows darken only the sun's.

The beach has a second shared block, the surf block, in the five shaders that have to agree on where the water is (The beach and the sea).

5.1.4. Frame fields, sun maps and texture units

Scene 3 gives the Frame block's fields its own meanings, listed at the top of scene3.c:

  • uViewProj / uLightVP: the camera and the sun (uViewProj is uLightVP in the sun-map passes)
  • uCam / uTime: the eye, and the scene's seconds after the stretch
  • uLightDir / uGlow: the sun's direction and radiance
  • uSkyColor / uGroundColor: the ambient from the canopy and the floor
  • uFogColor / uFog: the haze
  • uTop / uBottom: the sky
  • uWobble: the wind
  • uClarity: the macro amount
  • uReal: the beach part's progress
  • uRim: the offset from the clearing's sun map to the beach's
  • uSun: the offset to the characters' map
  • uBlobFog / uGape: the share of the sun the character on show lets through (0.3 for the blob, 0 for the ladybug and the tyrannosaurus), and the depth of its middle in that map
  • uQ: 1 in the sun-map passes, -1 in the impostors' bake, 0 otherwise

The sun has three depth maps. All three see the same sun over the same depth range, so a point has the same depth in each. A scale and an offset take a point's coordinates in the clearing's map to the others (uRim, uSun):

  • The clearing's map, 2048² over a box 72 m across (3.5 cm texels).
  • The beach's map, the same size, round a point 45 m out toward the sea. These two are still. A release build draws them only once, for a frozen moment half a second before the jungle opens, and the show draws them in its load's warm-up.
  • The characters' map, 1024², drawn every frame (S3_CHAR) in a tight box round whichever character is on show. The box is 6 cm across for the ladybug, 14 m for the tyrannosaurus, and 2.4 m for the blob, or 3.6 m once it carries the parasol.

All three are drawn through main.c's shadow_begin and shadow_end, which Scene 2's sun map uses too. The Light section has the rest: the frozen moment, the gaps cut for the sun, the characters' colour target, and how the taps are filtered and turned.

The texture units are fixed for the whole scene and all bound once:

  • 3: the characters' map's colour, how much of the sun each caster lets through (uSunT)
  • 4-6: the three sun maps (compared)
  • 7-8: the first two again, through a sampler that reads raw depth for the blocker search
  • 9: the copy of the picture the blob's jelly looks through
  • 10-12: the baked bark, leaf and sand tiles
  • 13-14: the far forest's impostor atlas (colour and depth)
  • 15: the sky's radiance table

The demo's 32³ noise volume sits on unit 1's 3D binding, which nothing else uses, since the post chain binds only 2D textures there. The shaders' value noise (vn3) is a single fetch from it. noise_make in main.c makes it first thing at load, so the bakes and the seabed's placement can read it too. After the scene is drawn, the post passes use units 0-6.

5.2. The trees (s3_trees.c, jt.vert / jt.frag)

s3_tree_sites places up to 300 trees. Eight hero trunks come first, set by yaw and distance where the look round frames them. Rings of jittered sites follow out to about 78 m, with giant buttressed broadleaves, palms and slender trees. Last come up to 140 understory palms and saplings (2.5-11 m), packed thickly round the clearing but clear of the viewer's path, the hero plant and the sky window. The beach part's own trees follow (below). Each frame the C side picks the trees in view, nearest first and grouped by kind, and issues one instanced draw per part and kind. The parts are:

  • lobed trunks that flare into plank buttresses
  • limbs
  • lianas and strangler roots spiralling up the giants
  • crown leaf cards
  • palm fronds with drooping leaflets
  • bromeliad and fern rosettes perched on the giants
  • curtains of hanging leaves
  • the far forest that every gap at eye level ends in

jt.frag cuts the leaves out of their cards. A card has two offset layers, and each leaf is tilted, veined and tinted on its own. Palm fronds are combs of 44 leaflets a side. Where the leaflets would come closer than about 3 px, they thin out by nested halves (44, 22, 11, 5.5), keeping every other leaflet and widening the ones kept, because finer leaflets flicker under the jitter. The sun's map uses 15. jt.frag also gives the bark its plates, lichen and moss, and turns leaves under about 2 px into stable clumps. Distant trees drop their lianas, epiphytes and planks and trade their cards for bigger ones, but only in the camera's passes. In the sun's map every tree keeps all its parts.

The planks grow out of the trunk's own centre line at each height (tc(y / top())), so they flare from it as buttresses and follow its lean. A giant's trunk wanders up to 0.07 H off its foot (2.8 m on a 40 m tree), so planks set out round the foot would stand clear of the trunk in the air.

A plank is a slab 20-40 cm thick, thickest low down, with a rounded crest. The slab's middle line climbs to one radius below the top edge by the shoulders and stays there while the crest rounds over it. The section's normal is normalize(cos across, s³ + s / 2 up), where s is the sine of the angle round it. So the normal is level at mid-height and leans up on the upper flank, where the moss gathers as on the trunk, and down on the lower one. Over the crest it turns up and catches the canopy's light. Planks get 8 columns round (16 on the near grid), so a vertex sits on each shoulder and on the crest. Seen end on, a plank is a solid, rounded fin.

A plank's occlusion also deepens in the corner where it leaves the trunk, from half the ambient right at the trunk to all of it 0.5 m out. It goes by how far the point stands out of the trunk. fin() hands that distance over, and the same distance lays the bark tile along the plank, one tile a metre. Neighbouring planks look like separate fins with shaded grooves between them. The planks cast sun shadows in the still maps too. They shade each other, the trunk they leave and the floor, and a plank on the far side of its neighbour from the sun lies in its shadow. At 20-40 cm thick they hold in the maps' 3.5 cm texels without acne, and since the maps are drawn once, the shadows cost nothing per frame.

In the picture, a giant's crown cards cut 9x9 leaves, about 0.45 m each. From the floor 20-40 m below, coarser leaves look like paint chips against the sky. The sun's map uses 6x6, and slender trees keep 4x4. Seen from below, a giant's card also has a solid heart in the picture. Within a ragged disc that reaches 30-70% of the way to the card's rim, where neither layer has a leaf, a third and deeper layer of smaller leaves shows (1.6 times the grid, with the mip to match). This layer is darker (occlusion 0.2-0.45) and lets little light through (0.1). Each clump is then a dark mass of foliage edged with lit, translucent leaves, as a real crown looks from below. Cards whose leaves fall back to clumps keep the heart, the sun's map keeps every gap, and the far forest's impostors match because the same program bakes them.

The wind is one gust field for the whole scene, gust(p, l). The same text is in jt.vert, ju_plant.vert and jb_leaf.vert, and the shader baker stores it once. It models a sea breeze blowing inland, with fronts about 31 m apart moving at 4 m/s. The fronts are bent across the wind and broken into patches that drift along them, shaped into gusts and calm spells, and scaled by the slow, global uWobble. l is a delay, asking for the field as it was l seconds earlier.

Each tree bends downwind with the gust at its foot, 0.6 s late. The CPU works that gust out once per tree (jt_gust, the same formula) and hands it over in the fractional part of the tree's seed, so no vertex evaluates the field. The bend follows a profile that is quadratic low down and about linear above 12 m, and a 35 m giant's crown moves about 0.3 m in a full gust. The tree's own sway and its leaves' flutter grow with the gust, and fronds move from the root out. Toward the forest's edge the breeze is stronger, three times as strong for palms and 1.5 times for other trees.

The far billboards work out the gust at their own feet, so their sway never depends on which trees were culled. The undergrowth leans and flutters with the gust at its root. The hero plant's leaves bob one after another as a gust passes, 0.15 s apart, except the hero leaf the ladybug walks on. The still sun maps hold the wind frozen at one moment (Light). The sway is small enough that the crowns' still shadows don't visibly disagree with them.

The trees fill most of the frame, so they light themselves with a cheaper jlit(). Next to s3light(), leaves get a plain glint in place of the GGX highlight and bark gets none, and there's no sky sheen. The glint broadens and its peak falls as the normal varies across a pixel, keeping distant leaves steadier in motion. The shadow is two hardware-filtered taps from the clearing's sun map, or the beach's past it, in place of the penumbra search. The pair of taps, 2.5 texels apart, is turned at random from point to point (Light). It reads the map on the sun's side by the common block's rule (above), which jlit writes as faceforward(N, -L, N), so backlit crown leaves, palm leaflets and curtains glow yellow-green against the sky. Bark gets the rough diffuse at 0.45, and the waxy leaves stay Lambert.

jlit's ambient adds the haze's golden light from the sun's side, uGlow × 0.06 × (0.5 + 0.5 N·L)³, because the humid air glows gold toward the sun. In the shade, which covers most of a trunk, the canopy's ambient lights every side of a vertical surface alike. With the golden term, a trunk rounds from gold to green-grey across its width, the bark's relief shows, and the planks' faces toward the light glow. On a trunk's sunward flank the term nearly doubles the ambient. It counts as ambient, so the screen-space occlusion still darkens it.

At load jt_bake.frag renders three material tiles once, and they stay bound on texture units 10-12. Each is 512², RGBA8, mipmapped and repeat-wrapped, with every channel periodic and 4x anisotropic filtering.

The bark tile is about 0.9 m square. It holds the height, a moss and lichen mask, and the height's two slopes, baked so the lit pass needs no extra taps. Its pattern is ridges wandering up the trunk, stretched Voronoi flakes with cracked rims, and lenticels. Trunks wrap it 8 times round a giant and 4 times round the rest, limbs and lianas map it by their own coordinates, and planks by the distance out of the trunk (above). Two sines of the height warp the tile's coordinates, shifting them round the trunk and stretching them along it, so its repeats never line up, and this takes no second fetch. Moss and lichen come from the mask at a quarter scale.

Each tree's bark takes a tone between a pale buff (0.19, 0.16, 0.12) and a dark grey-green (0.10, 0.10, 0.08), chosen by its random number. A tree's planks share their trunk's tone (jt.vert hands them piece 0's random), so they look like the same wood, with the same moss and lichen. The tone is streaked where rain runs down: the mask is fetched once more, stretched about 10 m up the trunk and 1 m round it, and scales the tone by 0.4-1.6. Lichen crusts are pale and thicker on some trunks, and the moss is a bright green.

The leaf sheet holds four 256 px variants of veins, tint, thickness and shape, with a drip tip, a finely toothed margin, one-sided insect bites, a midrib and side veins, and yellowed margins. The cards and curtains cut their leaves out of it, one variant per leaf. The sand tile, 2 m square, belongs to the beach (below). In each case a texture read replaces noise loops, and the mipmaps keep the detail from shimmering at any distance.

Within 6 m the bark gets a one-step parallax offset by the 3 cm relief under the point. Within 25 m (fading from 18 m) the baked slopes tilt the bark's normal, so the plates, ridges and cracks shade in the sun's flecks and the haze's light. That range takes in most of the trunks in the look round and at the vantage. As the slopes shrink on screen, the mipmaps and the anisotropic filter average them away, so they shade without sparkling.

Trunks and plank buttresses that are ever seen within 12 m are drawn on a finer grid, with twice the sides and more than twice the rings, so no facets show close up. The trunk's normal also leans with its lobes and flutes. Slender trees ever seen within 10 m grow modelled leaves in place of cards, 24 cards' worth of 10 leaves each. Every leaf is a drooping strip folded along its midrib, cut out of the leaf sheet and lit as a leaf with its real normal.

A tree sheds detail with distance. It drops its lianas past 20 m, its epiphytes past 15 m, its planks past 12 and 25 m, and its outer limbs past 30 m. Past 35 m it drops half its cards (the rest get larger) and half its fronds. The distance is the nearest the tree is ever seen from, not the current one. The camera's path is fixed, so at load s3_tree_lod steps the whole scene at 0.1 s with s3_setup and works out the nearest each tree's foot ever comes to the eye while the tree is in view. That distance picks all of the tree's detail once, so each tree keeps one level of detail for the whole scene and nothing pops in or out as the camera moves. It reaches jt.vert as the last of the tree's eight uT floats, and trees_draw sorts by it. The impostors' bake uses the true distance.

The far forest is baked at load too. The first four hero trees go into an atlas: the two giants (44 and 40 m), the slender tree (34 m) and a palm (15 m). Each is moved far out of every sun map and drawn from eight sides by the same jt program at full detail (uQ = -1). The atlas holds 8x4 views of 512x768 (21 texels a metre, 4096x3072 in all), with lit colour (RGBA16F) and depth. Each view is drawn in an orthographic box fitted to its tree, under Scene 3's fixed sun and sky with the haze off. Since the sun and sky never move, every side is lit correctly.

In the picture, 240 billboards stand 46-110 m out, drawn nearest first. Each faces the eye and blends the two baked views either side of the direction it is seen from. The baked depth, read through a linear filter, says how much of each pixel the tree covers, since a tree stands about 0.3-0.7 deep in its box and empty texels are at 1. The coverage is cut with a per-pixel, per-frame dither between 0.1 and 0.9 of a texel's coverage, as the spray's drops are, so the TAA spreads each outline over the filtered texel. The cut stays above 0.1, because a pixel with almost no coverage would be divided by about 0 and show as a black block. The haze is added live.

A wall of 40 more quads, 118 m out, closes off the distance with a canopy lost in the haze. It draws a ragged crown line, with crowns in three sizes: 12, 6 and 3 m across their cells, and at most 7, 3.5 and 1.75 m high. The small ones are clumped on the big ones. Each crown takes its cell's hash for both its width and its height (sqrt(g - w²) (0.4 + g)), so wide, tall crowns stand among narrow, low ones and gaps, and no row of matching arches forms against the sky. Their edges are ragged leaves at two scales, the second octave three times finer (about 0.3 m). The wall is darker in the trunks' depths, lighter toward the sunlit tops, and mottled.

In the beach part, the wall's quads more than 40 m out toward the sea are dropped, or its end would stand over the water past the cove's flank as a grey slab. The far forest keeps each quad's flat depth, because writing gl_FragDepth would turn off the early depth test and cost about 3 ms. A far quad that is culled goes as a whole: NM is zeroed first, so its corners all land on the origin and the leaves' flutter can't spread them. The wall's quads are judged at their middle. Judged corner by corner, a quad at the cove's edge would keep one side at 118 m and pull the other to the clearing's middle, as a wedge of wall across the look round.

The beach part reshapes the forest without moving it. s3_tree_sites places the 300 forest sites as for the forest alone, then marks the ones that have to go as gone (kind 3) without removing them. Every other tree keeps its index and with it its seed, so the shots before the glide don't depend on the beach part. Trees in the camera's glide (s3_roam_dist) step aside across it. Past the edge, a cove 48 m wide is cleared, along with the tyrannosaurus's track and the view out from the vantage. That view is cleared wider on its left, so nothing stands in front of where the tyrannosaurus is first seen. Fourteen hand-placed sites follow (S3_MAXTREE 320):

  • the pair of giants, 10.5 m apart, that frame the view from the vantage
  • a third giant left of them (28 m out, 9.5 m left), and the beast shows between it and the left one of the pair before it crosses the middle of the frame
  • four giants along the edge, whose crowns shade the track
  • two palms leaning out from the edge, and five on the sand leaning out to sea

The far forest opens across the cove, and round the cove's flanks too once the camera heads for the beach, so the view out ends in sky and sea.

5.3. The forest floor and the undergrowth (s3_under.c, ju_*)

The floor is a polar grid of 160 sectors and 80 rings round the viewer's spot, lifted onto s3h(). Its rings are a couple of centimetres apart at the centre and spread out geometrically to about 170 m. It's drawn after the plants, so whatever stands on it hides it early, and it takes sun shadows but casts none. ju_ground.frag stacks drifts of fallen leaves and twigs over damp humus. The leaves come in three sizes, the smallest only close up, from fresh ochre to rotting brown with the odd red one, and each piece is cut, veined and curled. Moss carpets the rises and the trunks' feet, where the floor also sits in shade. Once a leaf is smaller than a pixel, the detail fades to its average colour. The fine soil and moss noise also blends toward its mean as the pixel's footprint grows, so unresolved grains do not crawl across the floor.

Every plant is a bent strip in ju_plant.vert. The shader scatters 4,500 grass tufts of 16 blades by itself. The rest come from a table of up to 1,000 entries, placed on the CPU nearest first and clear of the trunks, the hero plant and a polyline through the camera's keys. They are ferns with curling tips, broad leaves (heart, arrow, holed monstera, torn banana-like paddles) and red-and-yellow heliconias. ju_plant.frag cuts each leaf's outline, veins and colour, and the cut-outs apply in the shadow pass too. Plants within 25 m cast sun shadows. In the picture every plant is drawn on its fine grid for the whole scene, so none changes shape as the eye comes by, and the sun's map gets the coarse ones. For the beach part the table is filtered after placement, so the clearing keeps its plants:

  • nothing grows on the sand
  • the undergrowth thins out toward the edge
  • nothing tall stands in the camera's glide
  • no broad leaf stands within 4.2 m of where the camera crouches for the tyrannosaurus. From 0.48 m up on a long lens, a seedling's leaf 4 m from the eye shows as a near-black two-faceted "clam" under the beast.
  • sixty low creepers trail along the top of the beach

Near the beach vantage, rooted ferns bow on the tyrannosaurus's thirteen footfalls, starting at jungle 52.26 s. The bend falls off with distance from a point 18 m along the beach's frame, reaching about 0.25 radians at its strongest. It changes the frond's arc itself, so the tangent normal follows the bend. The cached vegetation shadows keep their static pose.

The growl at jungle 58.9 s releases four broad leaves from the right canopy, 0.15 s apart. Each is about 0.3 m long and uses two of the existing plant strips, eight instances in one extra draw. They rotate and flutter into view from above, then fall below the floor before their draw ends at 63 s. They use opaque leaf geometry and add no shadow pass.

The beach's sand reads the baked sand tile, which holds 8 cm wind ripples over broader swells in patches, the grain's tone, and specks of bright shell and dark minerals. Its tiling is broken up with iq's third technique (tnt()). A slow noise picks two of eight random offsets of the tile and blends them with a height-biased step, so no 2 m repeat shows across the beach. Within 8 m, a four-step parallax occlusion march along the view into the ripples' 1.5 cm relief makes them stand up off the surface. Derivatives for the tile's footprint are taken outside any per-pixel branch, as in jt.frag, because inside a branch they are undefined and a frame would come out differently from run to run.

5.4. Light (s3_light.c, jl_* and the engine's post)

It's a bright, humid day. The sun stands about 48° up, to the front-left of the opening view, and is warm (uGlow 5.8, 5.0, 3.7). The shade gets green-filtered daylight, and the haze is a dark green-teal. light_frame puts these terms and the sun's matrices into the Frame block every frame. The rest of s3_light.c runs at fixed points in render_scene3:

  • light_shadow, before the world: the sky's table, the sun's still maps (drawn once) and the characters' map (drawn every frame)
  • light_sky, after the world: the sky behind everything
  • light_volume, after the late pass: the light shafts
  • light_post: the grade and the depth of field, to which render_scene3 adds the reveal's glare, the take-off's swell and the beach's long lens

5.4.1. The sky and the air (jl_sky.frag, jl_atmo.frag)

jl_sky.frag draws the sky as a full-screen pass on the far plane. It runs after the world, so only the gaps in the leaves pay for it. It draws s3sky, a limb-darkened sun disc, its aureole and its glare. There is no separate cloud pass or cloud history buffer in the current build.

The open sky is a planet's atmosphere and not a gradient. jl_atmo.frag marches it every frame into a 64x32 RGBA16F table on texture unit 15. The table's columns go by the angle round from the sun's bearing. Its rows go by the square root of the height above the horizon, so it's finest near the horizon, where the haze changes fastest. Everything that looks at the open sky reads the table through s3sky(): the sky pass, the reflections in the sea and the wet sand, and the haze over the beach.

After the physical scattering, the same table blends in broad, thin wisps from two smooth value-noise octaves. They drift slowly with time and stay near the horizon: they fade up just above it and out between about 7° and 17° elevation, with at most a 0.32 blend. This adds work only at the table's 2,048 texels, and the existing sky lookups put the same wisps in the sea and wet sand. They need no additional render target, history buffer or cloud shadow pass.

Each texel is a ray from the ground through the air in 24 steps, closer together near the eye, with Hillaire's 2020 constants:

  • Rayleigh scattering (scale height 8 km)
  • Mie scattering with a Cornette-Shanks phase (g 0.8, which gives the sun's aureole), 10% absorbed, with a scale height of 1.2 km. Its turbidity is the humid tropics': three times the standard in the forest, easing to the standard over the glide out toward the beach (uReal).
  • ozone absorption peaking 25 km up
  • the sun's light through flat layers (it stands 48° up)
  • a small isotropic share for multiple scattering

The table costs about 0.05 ms a frame.

5.4.2. The sun's shadow maps

The sun's main shadow map is a fixed 2048² box covering 36 m round the clearing, with 3.5 cm texels. A second map of the same size (uShadow2, texture unit 5) covers the beach and the forest's edge round a point 45 m out. It sees the same sun from the same depths (60-260 m), slid across. A scale and an offset in uRim take a point's clearing-map coordinates to the beach map's, and s3shadow() reads the beach map where the clearing's runs out. Beyond both maps the forest's canopy is taken to let a fifth of the sun through, and the open beach and sea get all of it.

The sun and the casters stand still, so the release build draws both maps once, for one frozen moment: jungle time -0.5 s, the first frame the show draws under the white. Whichever frame draws them, the whole Frame block is set for that moment and then put back. That fixes the wind, the eye position that turns the cards and thins the curtains of lianas, and the beach part's clearing. So the maps come out the same whether the show, a still, a seek or the load's warm-up draws them first. In practice the warm-up does (see The demo as a case study). The dev build redraws them every frame so hot reloads show.

A 12-texel patch over the hero leaf is cleared to a depth just under it, so a fleck of sun lands on the leaf and the ladybug and a shaft stands over them in the haze. For the sun's maps alone (uQ), jt.vert also drops the crown cards within 8 m of the sun's ray through a point 2.5 m above the tyrannosaurus's stopping place. That opens a gap in the canopy, out of view above, and puts a pool of sun on its track, which it walks into out of the shade. From the forest we see it near its terminator, and without the pool its dark hide would be a flat cut-out. The same pool lights the top of the beach, so the foreground of the beach shots is part sun and part palm and crown shade. The maps are drawn once, so none of this costs anything per frame.

5.4.3. The characters' shadow map

Things that move can't cast into maps drawn once. So a third map (uShadow3, unit 6), 1024², is drawn every frame (S3_CHAR) in a tight box round whoever is on show. That's 6 cm across for the ladybug while the macro rig is in, then 14 m round the tyrannosaurus's hips, then 2.4 m round the blob, widening to 3.6 m once it holds its things. The map has the same sun and depth range, slid across the sun's rays, so uSun takes clearing-map coordinates to it. s3shadow() multiplies in one tap of it, with its normal offset and bias scaled to the box. So the ladybug's feet, the tyrannosaurus and the blob cast real shadows on whatever lies under them, and on themselves. It costs 0.1-0.4 ms. With no one on show there is no pass, and every lookup lands outside the box.

The map has an 8-bit colour target too, which takes 1 MB. The characters' programs draw into it with one colour output, as they draw into the picture, so the driver reuses the builds it made at load. With a depth-only target, Mesa's Intel driver rebuilds each program at its first draw, and with a cold shader cache that drops a frame as each character comes on screen. In the sun's map their shaders return at once and write a constant, so the colour costs nothing. For the ladybug and the tyrannosaurus it's 0. The blob writes how much of the sun each part lets through (uSunT, unit 3): 1 for its jelly and eyes, 0.5 for its parasol's cloth and 0 for the rest of what it carries (see The blob ashore).

The jelly lets 0.3 of the sun through (uBlobFog, set while the blob is on show). Its shadow's edge softens with the distance past the blob's middle along the sun's rays (uGape), as the light the jelly bends spreads out. The soft edge comes from one tap, turned by the point's hash, which the TAA averages. The ladybug's and the tyrannosaurus's shadows stay opaque and sharp.

The post's contact shadows stay on round the blob. The jelly, its eyes and its drops write an ambient share of 1, and the contact-shadow pass skips any pixel at 0.999 and over, so they darken only the sand round its foot. There's no caustic focus in the shadow. The map is centred on the blob's steady line and not on its hops, so a focus would slide out of the shadow on every hop.

5.4.4. How the shadows are filtered

All three maps are compared in hardware (GL_TEXTURE_COMPARE_MODE, linear filtering), so each tap is a 2x2 filtered compare. A sampler object reads the two still maps' raw depths (units 7 and 8) for the soft shadows' blocker search, taking the nearest texel both when magnifying and minifying. The search's taps are turned by a per-pixel hash, so texture()'s implicit level of detail mostly minifies them. But in a quad where a cut-out leaf has discarded some lanes, the derivatives read whatever those lanes held, which is undefined. With linear magnification those pixels would switch filter at random, and Scene 3's frames wouldn't repeat from run to run (see Determinism).

s3shadow() turns its four blocker and four filter taps by a hash of the point's world position, and the trees' jlit() turns its two by the same hash. That looks fixed per point, but the TAA's jitter moves the point under each pixel every frame, and the hash is chaotic far below a pixel. So the turns change every frame (eight jitter spots, eight turns per pixel) and the history averages them. The trees' pair of taps sits 2.5 texels apart along the turned direction. Fixed taps would draw the map's 3.5 cm texels as stair-steps and double edges across the sun's flecks on trunks lit at a grazing angle. Turned, they give soft, round edges.

The soft shadows' narrowest filter is 0.0003 uv (0.6 texel), for a blocker within about 2.4 m. It stays that narrow so the contact shadows under close leaves stay sharp.

5.4.5. Light shafts and haze (jl_vol.frag)

For the light shafts (jl_vol.frag), each view ray is marched through the haze in 24 steps at quarter resolution, against the sun's map, out to at most 50 m. The haze scatters strongly forward (Henyey-Greenstein with g 0.7, plus an isotropic share) and is thicker near the ground. A ray glows as the square of how much of it the sun lights. That's a stylised contrast, which keeps the real beams and all but removes the dappled veil between them.

The lit share is measured over at least 8 m of air. A short ray to something near, inside a beam, is lit all the way, so on its own it would square to 1. It would then take more veil than the longer, partly shaded ray past it, and a morpho in a sunbeam would wash out pale against the trunk behind it. So the stretch past the surface counts for the share only. Samples under the floor aren't air and count for nothing, so the floor and what stands on it keep their glow. The glow itself sums only the air in front of the surface, with its last step taken in part, so there are no contour bands.

Past the sun's map, where the canopy is unknown, the air counts as all but shaded. A little light there would give every distant pixel the same milky glow. The engine blurs the result depth-aware (mc_renderer_blur) and adds it at the grade (mc_post.vol_tex, vol) in gold: the sun's light times (1, 0.9, 0.65), scattering 0.24 per metre at the ground.

The haze (s3fog in the common block) is humid air lit two ways. From above it takes the canopy's green light, a deep teal-green in the shade (uFogColor 0.09, 0.19, 0.17) that gets brighter the higher you look. It also scatters the sun's light forward, so toward the sun it glows gold (uGlow × 0.012 / (1.3 - cos θ)², with θ the angle from the sun). The opening shots look into the light and so stand in backlit air, while looking away the haze stays cool and deep. It's dense enough (uFog 0.011 per metre, thicker near the ground) that the trunks recede in clear layers, each farther one lighter and closer to the air's colour. Out over the beach and the sea the air is thin sea air, which takes the sky's colour from s3sky().

The shafts fade out past the forest's edge, because the open air over the beach carries no veil. The current build omits the earlier version's 7,000 dust and pollen quads and their two shaders.

5.4.6. The grade and the depth of field

light_post sets up the engine's post chain (see The renderer and its post stack). The grade has ground-truth ambient occlusion in the wide shots (on each pixel's ambient share, with contact shadows on the sun's share), saturation 1.12, a light vignette and grain at 0.008. The exposure is 1.0 in the forest's wide shots, easing to 0.8 up close and to 0.8 again for the beach part. render_scene3 adds the reveal's glare and the take-off's swell on top.

Looking out from the forest, the exposure stays set for the forest, so the beach glows past the dark trunks and the tyrannosaurus is a shape against it. A tension envelope rises from S3_GAP (jungle 50.3 s) to S3_REX + 3 s (55.3 s), holds through the head turn, then fades from S3_SHORE (60 s) to S3_CLEAR (67.7 s). At full strength it lowers exposure by 18%, subtracts 0.13 from saturation and adds 0.18 to the vignette. Once the animal has gone and the camera steps out toward the trees' last row, the exposure adapts to the sun. It does so between S3_CLEAR (232.3 s) and 4 s after S3_BLOB (240.1 s), ending at about 0.42, while the saturation rises to 1.24.

The depth of field (mc_post.dof, focus) is the engine's quarter-resolution bokeh gather. The macro amount drives it, up to 32 px of blur at 720 lines on the ladybug, and it eases off in the seconds after the take-off. The focus is the distance to the ladybug. The gather opens small, bright points out of focus into soft, round discs: the sun in a dew drop behind the ladybug, the glints on its raised wing cases. It also spreads the hero plant's blurred stems and leaves over the sharp ferns behind them as the gaze comes down. How both work is told under the depth of field in The renderer and its post stack.

On the beach the lens lengthens, and the depth of field behaves like a long lens's. render_scene3 adds a blur at infinity that falls off as one over the focus distance: 0.015 × the picture's height / (focus × tan(fov/2)) pixels. It fades in over the blob's first 4 s, so the glimpse and the tyrannosaurus stay sharp. The focus is the blob's distance, so the blob is always sharp. Behind it as it waves, the blur is about 5 px at 1080 lines, so the surf and the horizon soften and the blob stands out. The sand at our feet softens the same way. In the last wide view it's about 1.5 px. The quarter-resolution pass runs through the whole beach act.

5.5. The hero plant and the ladybug (s3_bug.c, jb_*)

jb_leaf.vert grows seven glossy elephant-ear leaves on long stalks, each blade or stalk a 40x16 quad grid. The hero leaf is 0.40 m long and holds still. Its shape is mirrored on the CPU by jb_leaf_at, so the ladybug's walk and the camera's macro rig land on its surface. jb_leaf.frag gives the blades a pale midrib, curving side veins, finer veins and a net of tiny cells about 1.4 mm across. All of them are recessed into a waxy surface by derivative bump mapping. The bumps fade out where a millimetre is smaller than a pixel. The blades glow when the sun is behind them.

The wax mirrors the gaps in the canopy at the brightness of the open sky at the horizon (uBottom), as broad, soft sheens blurred by the blade's roughness of 0.28. Every vein groove and the net of cells is tilted out of the mirror direction, so each one cuts a sharp dark line through a sheen. That's how the fine relief catches the light, as it does on a real waxy leaf. With dimmer gaps the wax would mirror next to nothing at the macro rig's 20-34°, and the leaf would look like flat paint. Under the ladybug the leaf takes a contact shade and a sun shadow traced against the bug's shell, which the 3.5 cm shadow map could never resolve.

The ladybug is about 8 mm long. jb_bug.vert builds it in millimetres from one unit sphere. The shader also makes the sphere, from gl_VertexID, as 12x12 cells on each face of a cube, with grid lines on the planes where the wing cases fold. Each instance is one squashed or folded copy of the sphere, and together they make:

  • red wing cases with the seven spots
  • the black shield with its cream shoulder patches
  • cream cheeks and glossy eyes
  • a ribbed belly
  • six three-segment legs, brown at their joints, stepping in a tripod gait
  • clubbed antennae that twitch now and then
  • three dew drops on the leaf

The bug is lacquered. A clear coat over both the red and the black mirrors the canopy's gaps as sharp windows of the open sky, at six times s3sky. The window round the sun is the gap its fleck of light comes through. The coat also catches the sun as a hard spark, which reads the share of sun s3lit found and so needs no shadow lookup of its own. Both are laid on at half weight, so you see the red first and the gloss second. At full weight the windows and the spark would cover the raised wing cases in white at the take-off. The weight is 0.5 on the shell, the black parts and the legs, and 0.6 on the eyes.

The red is the pigment under the coat: albedo (0.4, 0.016, 0.005), darker toward the rim, with roughness 0.7. Its gloss comes from the coat. A brighter red would sit on the tone curve's shoulder, which rolls bright colours toward white, and come out salmon. The black parts take the coat at roughness 0.4, like glossy chitin, and the shoulder patches and cheeks are a cream of about 0.5.

When the wing cases are raised for the flight they let the sun through, at s3lit's translucency of 0.4. Seen from below as the bug flies off, they glow orange-red round the black body.

The dew drops are dark at the rim, where the view bends out of them toward the dark canopy. Their colour falls off as n·v³, scaled by 2.4 so the core keeps its brightness, and that makes them look like water instead of lime gel.

s3_bug_at() gives its pose. The bug sets off as we come in, at 185.7 s, stops to look round from 194.5 to 196.8 s, and arrives at 202.3 s as it gets ready to fly. In that time it walks 77.7 mm along the leaf, weaving a little beside the midrib. jb_path() takes its heading from the path, so it faces the way it walks and doesn't crab sideways.

Its feet stay planted. While a foot is down it slides back under the body on a triangle wave, at the same speed as the body goes forward, and then it swings forward in an arch half a millimetre high. The gait's phase is the distance walked: 245 rad for the whole walk, or 39 strides of 2 mm. So the bug stops on all six feet at the look round and at the end. A planted foot drifts 0.3-0.7 mm/s. In the show each leg steps at most about 3.2 times a second before the look round and 5.3 after it.

At S3_FLY (203.2 s) the wing cases lift and spread. The smoky, iridescent hind wings unfold and start to beat, drawn as eight faint copies spread over the stroke to give the motion blur of an 85 Hz beat. At 204.9 s, 1.7 s later, the bug tucks up its legs and lifts off. It flies up and away toward the light, nose high and weaving a little. The macro rig stays where it was and tilts up to watch it go (see The camera).

5.6. Birds and butterflies (s3_birds.c, jv_bird.*)

One instanced call draws 17 creatures, and jv_bird.vert generates all their routes, shapes and wingbeats:

  • the four scarlet macaws that cross the canopy gap, with red bodies, long blue-tipped tails, and red and dull old gold underwings. Each wing's hand tapers to a point, with its five primaries splayed into fingers at the tip.
  • nine green parrots higher up, going the other way
  • a far pair between the trunks during the look round
  • two blue morphos fluttering past near the eye. They have no tail, because a tail strip would have zero length and zero normals, and the NaN that gives would flash the frame black (see the engineering notes).

They're drawn until 1.5 s after the gaze starts down from the canopy (S3_FIND, 181.9 s).

A morpho's wing is a fan from the thorax. jv_bird.vert runs u out from the thorax and v round it, from the leading edge, which is swept 0.25 rad forward, back along the abdomen to -1.45 rad. So the forewing's apex leads and the hindwing tucks in behind, as on a real morpho. The root is a 0.15-long stretch of the thorax and not a point, so no normal comes out zero. A zero normal would give a NaN, and the bloom would spread it. The wing beats from -52° to +75°, 3.5 times a jungle second, which is about three times a second in the show. The outer wing lags the root by 0.7 rad of the stroke, so the wing bends as it beats and its shading runs across it.

jv_bird.frag cuts the outline round the fan: the forewing's apex and outer edge, a notch, then the round, scalloped hindwing. The cut is a ramp one pixel either side of the outline, with the distance in pixels taken from fwidth. It is dithered per pixel and frame like the spray's drops, so the TAA blends a covered fraction there. A hard cut would stair-step along the scallops and the apex as the wings beat, because it moves several pixels a frame, more than the jitter alone can resolve.

The shader paints the top of the wing metallic blue inside a broad black rim. The underside is a cool dark brown (0.06, 0.045, 0.04). It is dark at the root, paler in a band round a row of five cream-ringed eyespots with white pupils, and darker again at the margin, at 0.36, 1.6 and 0.84 of the brown. So a wing seen from below looks like a wing. The brown is nearly neutral, so it takes the beam's gold as a tint, and the rings are cream (0.4, 0.36, 0.28). A warmer, paler brown looks ochre in the golden beams. Because the shafts measure their lit share over at least 8 m (see Light shafts and haze), a morpho in a sunbeam shows dark wings, eyespots and a dark body against the lit trunk behind it.

Feathers let 0.4 of the sun through, and a morpho's scaled wings 0.1. The wingbeat has a power stroke, a folded recovery and the odd glide, and the birds bank into their swerves. Every creature enters and leaves off-screen.

5.7. The beach and the sea (s3_beach.c, jw_sea.*, and the sand in ju_ground.frag)

The sea that draws the waves, the sand they run up, the reflections in them and the blob coming out of them must all agree on where the water is. So the four shaders that need it, jw_sea.vert, jw_sea.frag, ju_ground.frag and jo_blob.frag, carry a second shared GLSL block, the surf block, after the s3 common block (see How it's put together). It sits between its own marker comments (// ---- surf: … // ---- end surf ----), it is byte for byte the same in all four, and the beach package owns it. To change it, edit one copy and copy it over the others. The shader baker stores it once. It holds the swell:

  • jwph: its phase. The swell comes in every 7 s (6.98 s) and slows as the sand shelves under it, by c = √(g d) with the depth growing 6 cm per metre past the waterline. So its phase is the integral of the wavenumber, and crests 76 m apart far out bunch up to about 9 m apart at the waterline. They come in at a slight angle and bend over the bars. The phase is shifted by the tyrannosaurus's hold (S3_HOLD, 7.74 s), so the waves keep time with the blob's play.
  • jwg: the sets. A slow envelope over the wave count makes the waves come in sets of bigger and smaller ones.
  • jws: the swell's state at a point, meaning its height, how far past breaking it is, its phase and the wave's height. A wave grows as the water shallows, by Green's law (H ∝ d^-1/4). It is 0.3 m where the water is 10 m deep, times the set, times a variation along the crest that drifts from wave to wave. It grows until it is 0.78 of the depth, which is depth-limited breaking, 20% sooner or later over the bars. The crest is higher in places, so each wave breaks first at its peaks and the break peels along it. Before it breaks, its crest sharpens and its face steepens, and after, it is a bore half the depth high, with a long back and a steep front. The sharpening (m = 0.5) is the most the sea's grid can follow, and the vertices and the shading use the same value, so the silhouette and the normals agree.
  • jwr: the swash. It gives where the water's edge is now (.x), how far this wave's sheet runs, and the time since its bore reached the waterline. Each spent wave runs up the slope as a thin sheet in 1.75 s and drains over the next 4.5 s. The sheet runs 3 m times the wave's size, in lobes along the beach.
  • jwfilm: the film a draining sheet leaves on the sand, from its highest reach down to the retreating edge. It soaks in before the next sheet comes.
  • jwbed: the seabed out past the surf, sand with dark meadows of seagrass from 55-62 m out. The floor and the sea's own bottom, described below, both use it.

The sand belongs to the floor. Past the edge, ju_ground.frag thins the litter out onto pale sand with dune relief at two scales, wind ripples and, up close, glints from the grains. It lays a wrack line of weed, twigs and bits of shell at the top of the waves' reach. Below that line the sand is moist, on the lower beach and as far as this wave's sheet runs, following the edge. Moist sand is darker and less rough than dry sand, with a faint sheen. Each sheet that drains leaves a mirror-bright film that fills the ripples and reflects the sky, with a lace of foam stranded at its highest reach. That mirrored sky counts in the ambient share, so the occlusion darkens it too. Under the shallows the seabed shimmers with caustics, and seagrass grows out past the surf. The tyrannosaurus's three-toed prints, with a rim of pushed-up sand, appear where its feet came down, because the shader mirrors the footholds jr.vert plants. The blob leaves a wet trail up the beach.

The sea itself is one polar grid of 190 rings by 96 sectors, made in jw_sea.vert from gl_VertexID over the half-plane seaward of the upper beach. The rings are 30 cm apart for the first 30 m, through the surf, so a breaking crest's silhouette is smooth instead of a row of teeth. After that each ring is 7% wider than the last, out to about 2.7 km. (The camera's far plane is 2,500 m in this part.) The swell lifts the grid, fading out with distance where the vertices thin. Over the swash the grid lies on the sand as a thin sheet, which jw_sea.frag cuts off where the water's reach ends. The rings are drawn from the horizon in.

For the surface, jw_sea.frag takes the swell's slope from the surf block and sums the wind's waves over it per pixel. There are 17 trains from 18 m down to 8 cm, each with 1.4 times the wavenumber of the last, moving at deep-water speeds. They are all about equally steep, with a k from 0.045 for the long ones to 0.06, which gives a slope variance of about 0.028 in all. That is what Cox and Munk measured for a breeze of about 4 m/s. Their headings step by the golden angle, folded to within 90° of the onshore breeze, and they are short-crested: each one's phase wanders along its crests. Gust patches (cat's paws) scale the short ones, and over the swash sheet they calm down. Each train is filtered against the pixel's footprint along its own direction, fading out over 0.8-1.6 radians of phase across the pixel. A train the pixel can't resolve skips its cosine and adds its slope variance instead, so detail too fine to draw turns into roughness and doesn't shimmer.

The optics follow from that variance. The mirror normal is tilted toward the eye by one standard deviation, since the facets that face the eye dominate at a glancing angle. The Fresnel term uses water's index of 1.33 (F0 0.02), and the sun's highlight is GGX with α² twice the unresolved slope variance, widened further by the resolved normal's variation across a pixel. So with the sun off to the side the glints are rare, as they are in life. Far out, the sea is the deep blue of real water, darker than the horizon sky, and what it mirrors is the sky itself, s3sky along the reflected ray. This filtering already does what a glitter filter would, so there is none.

Whitecaps come from a second sum. Out past the surf zone, from 56-64 m, the four longest wind-wave trains, the 18, 12.8, 9.1 and 6.5 m ones that make up the wind sea proper, are summed a second time. They get a loop of their own because Mesa's compiler won't unroll a single loop over all of them. This second loop gives their heights and the crests' convergence, which is the choppy sea's Jacobian, after Tessendorf. A whitecap forms where the Jacobian falls to about 0.8, sooner in a gust. The loop also gives its gradient and its value 0.8 s earlier, so a crest leaves foam where it broke for about a second. Coverage is a smooth step across that threshold, with its width set by the slopes the pixel can't resolve and by the pixel's footprint. So it is sharp where the waves are drawn and soft over the footprint. Where the pixel hides the waves altogether, it becomes the chance of a whitecap, and the far sea whitens a touch. The light through the thin crests is green in proportion to their height. That is linear, so what a pixel can't resolve averages out instead of shimmering.

The sea draws with dual-source blending (GL_SRC1_COLOR, core since GL 3.3). Output 0 adds what the water scatters and reflects, and output 1 multiplies what lies beneath by the water's transmittance. The sea writes alpha 0 that way, so the ambient share of what lies beneath stays as it was. The transmittance is absorption per channel, 0.45, 0.07 and 0.04 per metre for red, green and blue, along the refracted path down to the bottom and back up. Past 80 m the bottom drops away. The floor's mesh ends about 80 m out, so beyond it the sea draws a bottom of its own, wet sand and seagrass from jwbed. The two are cross-faded over 60-75 m by where the eye's ray meets the floor, so there is no seam. The water's own colour is turquoise over the sand, greener in the shallows, deep blue far out, and a milky pale green where the surf has churned air into it. Seen through, a wave's face crosses more water the steeper it stands, so it looks greener and darker. Just before a wave breaks, its thin lip glows green, lit through from behind, over the shade of the face below.

White water is computed only where there can be any: broken water, the swash's edge, and round the blob. It is made from warped noise for the streaks and lumps and a net of ridges for the lace. It fades into its average as the pixel's footprint grows, so far foam averages out instead of aliasing. A wave is whitest just after it breaks. The whole front of the bore is white, a trail decays behind it, and a low background covers the surf zone, thinning as the bore runs in. The wave breaks harder in patches along the beach and from wave to wave, driven by continuous noise so that nothing pops. The puffs along its front ride with it and take half the pattern there, so the foam's own warped noise breaks them up. The foam's lumps are lit by the sun on top, and in the hollows by the sky and the white around them, which gives a pale blue-grey. Finer octaves give a torn edge, and the foam's cut-off is soft, 0.2 wide close up and widening to 0.5 with the footprint, so the edges fray.

So the crest's white water is a run of ragged, broken lobes. The swash's leading edge is a lace. Round the blob there is a collar, plus the froth it churns up as it breaks the surface. The collar is 0.5 and 0.3 of the foam where it cuts the water. Any more fills it in to a smooth pale ellipse.

The sea is drawn in one pass after the floor, from the horizon in, so nearer water blends over what lies behind it. It writes its own depth, which is the surface pushed on along the eye's ray by up to 1.2 m but never past the floor. It does that through a gl_FragDepth declared depth_greater, a promise to only ever push depth farther. That keeps the early depth test, so what the trees hide is still rejected before shading, which is worth about 1 ms in the tyrannosaurus shot. The ambient occlusion, the contact shadows and the depth of field all see this one smooth surface, with no blotches where the shallows meet deeper water and no seam where the floor ends. Because that depth lies under the surface, the blob's body under the water, drawn later in S3_LATE, passes the depth test. In the far distance the sea fogs into the horizon through s3fog().

The sea and wet sand keep their Fresnel-weighted reflection of the sky's radiance table, evaluated in their material shaders. The current build omits the half-resolution screen-space reflection pass and its render target. Nearby objects, including the blob, therefore no longer appear mirrored in the water or wet sand. The swell, swash, foam, water depth and the blob's refraction remain part of the scene.

5.8. The tyrannosaurus (s3_rex.c, jr.*)

The tyrannosaurus is sculpted once, at load, by the creature kit (jr.comp), which also makes Scene 2's fish, crabs, predator and whale. The kit is one compute program whose pass number picks the animal and its grid as well as the pass. jr.comp describes the tyrannosaurus in its rest pose as a smooth union of about forty round cones and ellipsoids:

  • the deep-chested trunk, from the tail's tip to the neck
  • a wedge of a skull with jaw muscles and brow ridges
  • the lower jaw
  • on each leg, a great drumstick of a thigh, the calf, the long foot and three clawed toes
  • two tiny two-clawed arms.

Each part is melted into the whole by a polynomial smooth minimum and belongs to one of twelve bones: the trunk, the head, the jaw, each leg's thigh, shin, foot and toes, and the arms. The folds round the neck are a small displacement.

Three compute passes turn this into a mesh. The first evaluates the field at every corner of a 5 cm grid of 49x123x278. The second puts a vertex in every cell the surface crosses, at the mean of its edges' crossings (surface nets). It gives the vertex the field's gradient for a normal, and the two bones whose parts lie nearest, blended by distance. The third adds a quad to the animal's own index buffer round every grid edge the surface crosses. At load, uw_sculpt in main.c runs the three passes for each of the five animals in turn. It uses one dispatch size for all of them, since each pass returns early past its animal's grid. They all write into one shared vertex buffer, which stays bound on storage-buffer binding 5 for jr.vert and fish.vert. uw_sculpt reads each animal's triangle count back, so each one is an ordinary indexed mesh drawn with mc_mesh_draw or in instance batches, and then it frees the grid's two scratch buffers. The tyrannosaurus is about 38,000 vertices and 76,000 triangles, and the whole kit takes about 60 ms of GPU time at load. map(), the animals' field, has a single call site, which the vertex pass loops over for the field, its three normal steps and the vertex. So the GPU compiler inlines the five animals only once, and the kit compiles in 0.3-0.7 s cold. A change to jr.comp shows only after a restart, as there is no hot re-sculpt.

The animal is about 13 m long, with its hips about 3.55 m up. Each leg melts into the body only, never into the other leg: the body's field is united with each leg separately, and the two results are joined by a plain minimum. If the legs melted into each other, the skin between them would follow both thigh bones half and half, and the stride would stretch it into a web. The thighs are slim from side to side, with the cone squeezed to half its width and the drumstick 0.35 m half-wide. From the front they hang apart under the belly, and each melts into the body over 40 cm, which leaves a fillet behind it.

jr.vert poses it every frame. Its hips travel along the edge at 3 m/s and stop in the gap between the two trunks at 58.1 jungle seconds (222.6 s), where it looks at us. s3_rex_at on the CPU, across() in the shader and the prints in ju_ground.frag all agree on this. From S3_REX a foot comes down every S3_STEP, left and right in turn. Each leg reaches its foothold by two-bone IK while the body bobs, sways and rolls over the stance foot and the tail swings the other way. Each vertex follows its two bones, blended linearly. The trunk bends along its length for the tail's waves and for the neck turning with the head. The head and the jaw turn rigidly with the look, and each leg's bones are carried rigidly from their rest joints onto the IK's.

The skull is wide low down and narrow on top, so it looks like a tyrannosaur from any side, head-on included. It has broad cheeks and jaw muscles behind small forward-looking eyes under brow ridges, and a deep, narrow snout. The cheeks reach down over the hinge of a lower jaw that is deep at the back and U-shaped in section. The closed mesh culls its back faces. In the gap the tyrannosaurus turns almost directly toward the camera from jungle 56.2 to 57.4 s and pitches its head down. The jaw opens wider on the two snorts at 57.93 and 58.54 s. It growls, then turns away from 59 to 60.4 s and walks on.

Its hide (jr.frag) is a dark, rough brown of albedo 0.05-0.11, paler underneath, with near-black bands, 85% darker, across the back and the tail that fade down the flanks. A brighter olive looks like a khaki clay toy in the shade. The hide is mottled and pebbled with scales. Fine scale noise blends toward its mean as its pixel footprint grows, and the relief fades from 0.06 toward zero where it cannot be resolved. The scales still show on the long lens. The hide has a little sheen, at roughness 0.65 (any glossier and its back goes pale in the shade), and is creased with folds. All of this is computed from the rest-pose position, so it rides with the body. A wet amber eye sits in its socket, and teeth are painted along the mouth's seam on the side of the head. The mouth darkens as the jaw drops.

A broad fill from the horizon sky lifts the head's ambient light, strongest on upward-facing parts. It follows the head's rest coordinates and joins s3amb, so the occlusion still darkens the eyes, jaw folds and creases.

At grazing angles the hide mirrors the bright beach and sky behind it: s3sky along the reflected ray, with Schlick's Fresnel, dimmed where that ray finds the shaded floor. That makes a rim which draws its back, head, neck and tail against the background. The rim's window counts N.y with R.y, so it leaves out the undersides, which only graze the horizon from under the body. Without that, seen edge-on, the belly and the thighs' undersides mirror the bright horizon as a cyan glow between the legs. The rim also leaves out the skin between the thighs under the belly, which the legs and the belly close in, and there the rim and the hide's occlusion are cut to a fifth. The rim is sky light, so it counts with the ambient share, which the screen-space occlusion darkens in the folds. It stays out of the sun's share because the contact shadows, marching from the flank behind the near thigh, would find the thigh and leave a dark slit where it meets the body.

Two flaws are left. At the stride's forward extreme, at 223.2 s, there is a thin dark band where the back of the near thigh meets the belly. Better skinning, with dual quaternions or corrective weights, would fix it. And a sliver of sky shows on the inner thigh, 0.2 px wide at 1080p, where the mirror is already cut.

A little light also comes through the thin parts on its far side from the sun, such as the snout's tip, an arm and the toes. This is Barré-Brisebois's translucency, with a thickness taken from the curvature of the interpolated normal. At 21 m it amounts to a few warm pixels.

The animal is lit the way the open shade at the forest's edge would light it. It stands 2.5 m out past the edge, open to the sky over the beach, so its back and head take the sky's light at an occlusion of 0.9 and its belly stays dark at 0.3. With an albedo of about 0.05, a low occlusion all over turns it into a flat black cut-out, and a high one lets the open sky light its flanks grey. Where it stands in the pool of sun (see Light), a rim of sun draws its back, neck and head, so its silhouette stands out from the sky. The rim is uGlow × the sun's share × (1 - N·V)⁴ × N·L. In the shade, as it first walks into view (217.7 s), it stays a dark shape. Seen from the ferns, with only half the haze over it, it is still a dark shape against the bright sand.

The eye's cornea is a dome. Its normal is the point's offset from the eyeball's centre, which sits 5 cm in under the skin and is carried by the head's bone, and jr.vert hands that centre over. So the hide's mirror term gives the eye a small catchlight of the open sky over the beach on its upper part, moving as the head turns. Its reflectance is 0.2 head-on, far above a real cornea's 3%, to make the small highlight readable in the shade. It mirrors the sky's radiance table. The eye gets no glint of the sun, because it stands in shade in every frame, with the brow and the head between it and the sun.

It draws itself into the characters' sun map every frame (S3_CHAR), so it casts its real shadow and shades itself. It also multiplies in a contact patch of darker floor under its body and planted feet.

The camera stages it (see The camera). The draw starts four seconds before S3_REX, while the whole animal is still offscreen. Its appearance is continuous motion through real trunk occlusion: tiny glimpses around jungle 50-52 s, then the head and shoulders before the body clears the trees. There is no opacity fade. As it comes, the camera crouches into the ferns, looks up and lengthens the lens, so the beast stands against the sky in the gap. Each footfall (s3_rex_step, on the music's beats) drops the eye and dips the look. The drop has a 0.16 m gain attenuated by 1 / (1 + distance² / 225). The jolt rises over 40 ms, 1/24 of a step or about 2.5 frames at 60 Hz, before it decays. A drop within a single frame looks like a skipped frame instead of a thump. There are thirteen jolts, one for each of the music's footfalls, and they stop only once the thirteenth has died away.

The first snort, at 57.93 jungle seconds (222.4 s) in jr.vert's jaw and in the music, makes us flinch. A Gaussian pulse with a 0.24 s width moves the eye 0.35 m backward and 0.12 m upward at its peak, and lifts the look with it.

5.9. The blob ashore (s3_blob.c, jo_blob.*)

This is the blob of Scenes 1 and 2, the soft, glossy, slightly translucent blue jelly with two dark eyes. Here it is about a metre tall (S3_BLOB_R 0.48 m) and has grown two stubby arms, which are the jelly itself swelling out of its sides the way Ditto's do. Body and arms are one surface. The signed distance of the squashed ball is melted with a smooth minimum into a round cone per arm, so each arm grows out of a soft fillet with no seam. Each half of the jelly is a 72x48 fan of rays cast from inside its shoulder. That origin is inside both the ball and the arm, so every ray crosses the surface exactly once, even with the arm raised. The fan runs from the outward normal there to the seam down the blob's middle, where the two halves' rays meet at the same points. The vertex shader bisects each ray onto the surface and takes the normal from the field's gradient. One instanced draw makes the two halves, the two eyes and the things the blob carries, which come below. It draws into the characters' sun map too (S3_CHAR, from a grid half as fine), so it casts a real shadow on the sand and shades itself.

s3_blob_at gives its steady line, which the camera aims and focuses on. At S3_BLOB the blob is just under the surface about 13 m out. It comes in and up out of the sea, then up the beach to the wet sand about 4 m above the waterline, facing the land. jo_blob.vert times the performance around that line off uTime. Its cue times are jungle seconds less the hold (S3_HOLD, four bars or 7.74 s), and jo_blob.frag counts the same way. The blob rises, shakes off the water, then hops up the beach, nine hops at one a beat. Each hop is a wind-up squash, a stretch as it takes off with its arms up, and a squash on landing. Once it's ashore its bottom flattens against the sand. It settles, blinks, looks left and right, turns back to the camera and lifts both arms to wave. After the play it shivers once more on the dry sand to shake itself dry, a 6% wobble at 11 Hz from 262.8 to 263.5 s. Nothing flies off it there, 3.4 m up from the swash's edge, because only landings in the water splash. Spray there would look like water out of nowhere.

The play is a table of hops, JO_RUNS. Each run starts on a jungle beat, counted in beats since S3_J0 (bar 38 is beat 152). It gives the number of hops, the beats each takes, how far each carries the blob toward the sea or back up the beach, how high, how far each turns it, and how hard each landing splashes. The nine hops ashore are the first run. Then the blob turns round to the sea, hops down after a draining wave into the last of it, and bounces. It makes its two-beat jump over the next wave's foaming front, stamps, turns back and hops up the beach. Then it turns half round and takes a 25 cm step down toward the sea, to 46.65 m out, to sit and watch, with a last bounce as a wave laps round it. That step puts the last landing in the swash, with the sheet running 30 cm past it as it lands. The beats were picked against the surf block's swash, so the water really is where the blob lands. A wave comes every 6.98 s, and the set grows: three successive sheets run up to 47.6, 46.8 and 46.3 m out. s3_blob_at integrates the table for the blob's steady line and its turn. blob_draw hands jo_blob.vert the run in progress (hops into it, height, step, turn) and the time since the last run ended, for a settling wobble. So the shader shapes each hop, and none of the play is hard-coded there. The music's hop plucks and splash hisses follow the same table (see Its music).

For the splashes, jo_splashes finds the two latest landings in the water, with where, how long ago and how hard. Only landings in the water splash. They go to jo_blob.vert for the spray and to jw_sea.frag for the water. In the water each landing makes a ring of foam running out from it, of radius 0.5 + 1.3 √age m, with churned white at its heart, fading over about a second. The ring and the heart are lace and froth, weighted 0.4 and 0.5 and held under 0.55 of the foam, so they don't saturate into one flat disc of the foam's sky-lit lilac hollow colour.

While spray flies, the blob's draw gets two more instances. Each throws up to about 2,800 drops ballistically from a ring round the landing. The drops are 1-6 cm across, skewed small (h.z × h.w² of the range) so that there are a third as many big drops. Each drop is drawn as a quad turned to its velocity across the view and stretched by how far it flies in a 60th of a second, with its coverage spread over the streak. So the splash is a crown of streaks thrown up and out, where the fast drops are long streaks and the slow ones are still drops. A drop is never drawn smaller than 1.5 px. A smaller drop's coverage becomes its opacity instead, dithered, so the spray doesn't shimmer. Drops fade over 0.7-1.2 s and are gone once they're below the water or the sand.

jo_blob.frag shades a drop as a dithered disc. It refracts the picture behind it, mirrors the sky by Fresnel and catches the sun, using the same copy of the picture as the body. Only the mist is white from churned air. The mist is the drops under 1.5 px, whose coverage is their opacity, so they stay white and soft. A big drop is clear: the picture through it is undimmed, and the sky at its rim is at 0.4. No drop is drawn more than 0.7 opaque. The picture it refracts is copied before the blob is drawn, so a solid drop over the jelly would show the sea behind it as a white or sea-green disc. At 0.7 the jelly shows through and the TAA blends it. At 1:1 the spray is a ring of glinting, translucent beads round the blob.

The blob's jump into the swash (at 257.7 s, JO_PROPS) throws a straw sun hat, a beach parasol and a folded deckchair up out of the water. It comes out of the water for the last time with them. The hat sits at a slight tilt on its crown. The parasol is in its right hand, with the pole leaning in over its head, and the chair hangs from its left hand by the top bar. They are two more instances of the blob's own draw, which jo_blob.vert numbers first: 0-1 are what it carries, 2-3 the jelly, 4-5 the eyes and 6-7 the spray. So they ride the blob's frame through its hops, its squash, its turns and the shiver, and they ride its two arms, which take a holding pose as it lands.

The 72x48 grid's rows are split into parts, each one a lathe (a profile turned about an axis) or a straight tube. Instance 0 holds the canopy in 22 rows, the pole in 4 and the hat in 22. Instance 1 holds the chair's six rails and bars, two rows each, and its canvas in 6. A part's normal is taken across its own surface by finite differences, from two more evaluations of the point, so no part needs a normal written out.

The canopy's ribs run out from a hub 1.2 m up the pole, bowed down toward their tips. The cloth sags between them and is pulled in at their tips, and a valance runs round its edge, with a hem that jo_blob.frag cuts into scallops. Once caught, the parasol opens over half a second (258.1-258.6 s) as the ribs swing out from along the pole. It has eight red and white panels. The pole is wood, pointed at its foot, with its finial poking out of the hub. The hat is a round crown and a wide brim that droops and waves. It sits just above the jelly, because any lower the jelly's crown pokes through the brim. It is braided straw in rows about 1.2 cm wide, each row its own shade with a groove between them, under a navy band. The chair is a deckchair's two frames of varnished wood, with the striped yellow and white canvas folded between them. It hangs off the hand, turned a little out from the blob's side, and swings as each run of hops stops. The landing's splash throws all three up: for 0.45 s they rise from under the water in front of the blob, slowing down into its hands and onto its head.

They are opaque, so they draw with the world (S3_LIT). The copy of the picture that the jelly bends is taken after them, so they show through it, and the wet sand mirrors them. S3_LATE draws them again with the jelly, since the instance range starts at 0, but the depth test, GL_LESS on the very same depths, passes none of it. In the characters' sun map (S3_CHAR) they cast real shadows on the sand, and the canopy casts one over the hat and the blob. Once the blob carries them, the map's box round it widens from 1.2 m to 1.8 m to take in the canopy. The map's colour target lets the driver reuse the shader builds it made at load. It also holds how much of the sun each thing lets through (uSunT, unit 3, read in the s3 common block's s3shadow):

  • 1 for the jelly and its eyes, which keep letting uBlobFog (0.3) of the sun through
  • 0.5 for the parasol's cloth, which lets 0.15 of the sun through. The shade under a beach parasol is a light one. With an opaque cloth the hat under it goes a greenish grey.
  • 0 for the rest.

From the shade, the cloth and the canvas glow with the sun coming through them, and the straw glows a little.

The blob is drawn late (S3_LATE), after the sea and the sky. The demo copies the finished picture into jelly_copy (unit 9, the whole frame, as in Scene 2), and jo_blob.frag bends that copy through the blob's body under the coat's own lighting. The beach, the surf and the sky behind it are refracted per colour channel and absorbed by the blue along the chord through the body. Each channel's lookup is a vertical tent of three taps, with the middle one counted twice, spaced 2% of the picture's height times the chord's thickness apart. On the long lens the sea is horizontal streaks, and the ball flips a tall stretch of it and squeezes it into tight stripes. The tent melts the thin stripes into a few soft bands, while the broad bands of foam and water stay. The streaks are mostly the mirrored horizon in the swash and the surf, which is how a long lens shows a real sea.

The blob writes an ambient share of 1 in alpha, as its eyes and drops do, so the post's contact shadows pass over it. Otherwise their march takes the ball and the spray in the depth buffer for solid blockers, and blackens patches of the jelly. The jelly glows where the sun comes through it and mirrors the sky in its coat. It is wet and streaming as it comes out of the sea, and again as it comes out of the swash. Where it is still under the surf, it is shaded the way jw_sea shades the seabed, with the water's transmittance along the view, its scattered colour, and the sky mirrored at grazing angles. A ring of foam forms where the swell laps at it. The eyes are glossy black lenses that catch the sky.

5.10. The camera

In the wide shots the camera follows a Catmull-Rom path through eleven keys (S3_KEYS: yaw, pitch, field of view and the eye's offset over the ground), with a faint handheld drift. From the descent a second rig, the macro rig, takes over. It circles in on the ladybug (s3_bug_at) to 4.5 cm and orbits it slowly, and when the ladybug takes off it stays where it was and tilts up to watch it go. The near plane shrinks from 5 cm to 4 mm as the rig closes in, and the focus distance it hands the depth of field is the distance to the ladybug.

From S3_ROAM the glide takes over from the macro rig. The glide is a second Catmull-Rom path (S3_ROAM_KEYS), with positions in the beach's frame: distance out, height over the floor and distance across. It carries the eye from under the hero leaf, between the trunks, to the vantage point, S3_VANTAGE, and holds it there while the beast passes. The vantage is 13.5 m out and about 21 m back from the tyrannosaurus's track. As the beast comes, the eye crouches into the ferns, from 1.55 m at the glimpse to 0.48 m at jungle 57 s. It steps 1.6 m to the right of the glide's line and looks up, to a pitch of 8°. The lens tightens from 50° at the first footfall to 29° at 57 s, then eases to 31° at 60 s while the eye remains low. The look leans half-way toward a point 4.1 m above and 3 m forward of the beast's hips, so it follows the head. The beast stands against the sky in the gap between two trunks 10.5 m apart, then walks on behind the right-hand one.

The step to the right opens the view between the trunks as the eye crouches. Panning alone moves the trunk and the beast together; moving sideways changes their overlap and lets the head and shoulders emerge gradually. The trees and the undergrowth keep out of the glide by s3_roam_dist, the distance to it, and a tree in the way is pushed aside or dropped. So moving a key sideways can change the forest.

We hold, crouched, while it walks on out of the frame, and stand once it has gone (S3_CLEAR, 232.3 s). From about 233.8 s the eye steps out between the trunks to the trees' last row, about 22 m out, which it reaches at 238.8 s as the blob rises. We see the arrival wide, with the blob 2% of the frame high. Then the eye walks on out of the trees onto the top of the beach as the blob hops up toward us. The eye is 27.5 m out by 242.3 s and 31.5 m out by 249.8 s, and meanwhile the lens lengthens from 54° through 32° to 20°, then 18° for the play. It's one continuous dolly and zoom with no cut. The blob waves from about 15 m away, about a fifth of the frame high, so its arms, its wave, its hops and its splashes are easy to see.

Through the wave and the play the eye is about 1 m over the sand (1.25 m at 242.3 s). The blob sits in the lower third against the surf and the white water, with sky over it and sand below. The eye can't go much lower: at 0.7 m the sand rising in front of it cuts off the blob's feet and its landings, and the horizon rises nearly to the middle. Higher up, the lower part of the frame fills with dark sand in the trees' shadow. While the blob plays, the eye drifts 4.5 m sideways along the top of the beach, 16-17 m from it, and the sun comes round beyond the blob and lights its spray from behind. The lens reaches 17° for the catch, then eases to 18° as it leaves the swash. From 264.8 s the eye goes back among the trees, rising from 1.75 to 3.25 m and widening to 54° for the last wide view, leaving the blob small against the shore at the end of the fade.

The keys aim at the blob. Each key's yaw points from its eye to s3_blob_at, rounded to a half degree, and the pitches are rounded to quarters. Through the wave and the play the keys pitch 1.5-2° up, about 7° above the line to the blob. On top of that the look leans half-way toward the blob, which sets it a little below the middle and keeps it there on the long lens as it hops down and back. The focus follows whatever the camera is watching. The eye keeps at least 2 m from every trunk, and the pull-back passes the edge palm 32 m out on its right, out of frame. The beach's sun map covers everything the eye sees, and the blob's 72x48 grid holds up at 18% of the frame. From S3_VANTAGE on, the view out from the vantage is kept clear of trees.

6. The end card

After Scene 3's black (SCENE4_START, 269.0 s, bar 139), the show says hello once more in the style of Scene 1, for five bars (render_end). The picture is Scene 1's world, drawn by s1_draw (How a frame is drawn): the key light's shadows, the sky, the grid, the queued cubes and the blob. The finale's violet sky is darkened a little (0.55 at the top and 0.8 at the bottom) so the letters stand out, and a head-on camera eases in. The finale's sun (The finale's sun) stands behind the words and sets through the card, and its last bars sink under the horizon as the card fades. The card fades in over its first beat and out over its last three, beats 17 to 20 (277.3-278.7 s), along with the music. Then the program exits (see How it ends).

Some of the shared state has to be put back after Scene 3. s1_draw sets the glow colour every frame, because Scene 3 uses it for its sun. The blob's lumpiness (uWobble) is 0.15, the round blob that Scene 2's transformation leaves, which also keeps its eyes clear of the lumps at this size. And render_end sets uAtmo to 1 itself, meaning no shock and no outro dark (see The dark, the shock front and the outro). In the show Scene 2 leaves 1 there, but a seek from Scene 1's outro straight to the card would leave 2 and turn its sky black.

6.1. The card's words

build_text lays out the text, "HEL-LO/MI-CRO-COS-MIC/WORLD" (END_TEXT), right after the title's. Each / starts a new line, and the lines are centred, 12 cells apart. A - only ends a syllable. It makes 323 cubes after the title's 142, each tagged with its syllable (logo_syl), and records each syllable's top centre (syl) for the blob. The cubes fly in from their scattered starts over the first three beats and tumble until they land, as the title's do, and both use push_cube.

The letters light up like karaoke. A syllable dips and flashes as the blob lands on it, and stays lit after, at emission 0.45 against 0.12. The last landing's flash runs out from the blob through all the letters, timed by their distance.

6.2. The blob's hops

The blob (END_R, 0.7 m, about a letter's height across) makes eight hops. Each hop is a parabola that arcs toward the camera, so the blob passes in front of the lines it crosses. It takes off 0.3 of a beat after its last landing, which is the crouch, and lands on its beat (END_LAND). The first hop drops it from 7 m above onto HEL on beat 4 (271.0 s). Then it lands on one syllable a beat: LO, MI, CRO, COS, MIC and WORLD, on beat 10. The eighth hop is a somersault from WORLD up onto the top of HELLO, in the middle, landing on beat 14 (275.8 s). The blob stretches in the air and squashes through each landing, with its bottom staying on the letters. On top of HELLO it puffs up by half, and on beat 15.2 (276.4 s) it winks. The wink is the same as in Scene 1 (wink), and blob_eyes places the eyes on the turned, squashed body.

6.3. The card's blob

It's the beach's blob and not the title's, so the card follows on from Scene 3's glossy jelly. On the card only, blob.frag shades the blob the way jo_blob.frag shades the beach's (see The blob ashore). It recognises the card by its blob being round (uWobble 0.15) and not jelly (alpha 0). The title's blob is lumpy and the deep's is jelly, so neither of those changes. The card's blob gets its soft light at a third and its pale edge at half. The violet sky behind is seen through its blue by Beer-Lambert absorption, it mirrors the sky by Fresnel, and the window of sky above it shows on its crown as a soft glint. So it is deep blue and glossy, and stands out from the violet and the pastel letters. It is opaque, and it has the same eyes as the title's blob, catchlights and all (The blue blob).

It also has Scene 3's two little arms, which blob.vert grows on the card only. Each is a round-tipped paraboloid stub out of its side, a little forward, 0.42 of the blob's radius high and 0.27 from its axis to its foot. A smoothstep flares the foot into the body. The arms are one surface with the body and shaded like the rest, so they look like the jelly swelling into arms, with no seam. To follow them, the normals there are taken 0.02 rad apart instead of 0.1, and the blob's grid has 48x48 cells a face. The arms hang 29° down through the hops. As the blob puffs up for the wink they rise out to the sides. The left one goes to 17° up, cheering, and the right one waves between level and 26° up at 1.75 Hz as the card fades. Raised higher, beside the head, they'd look like ears.

In the somersault the blob draws its arms in as it turns over, or the hanging arms would stand up like horns. They grow back over the last third of the turn, most of the way in about a quarter of a second. That follows smoothstep(-0.5, 1, the height of the model's up-vector), so they're small while it's still tipped and full as it lands upright. Over a narrower range they'd pop back in a couple of frames.

6.4. The card's music

The card's music is a coda (BARS_END, section 28 in synth.c; see The softsynth for the rest of the soundtrack). Scene 1's movement comes back after the jungle with its kick, hats, bass, plucked lead and arpeggio. The leads rest through the jungle and play again from BAR_END. The coda comes straight in on the first kick, led into by a reverse swell over the last 12% of Scene 3's closing section, about 0.9 s. It plays Scene 1's progression once more, I V vi IV, and goes home to I on the last bar, where everything dies away with the picture. On top, the kalimba sings the words as the blob lands on them, one note per syllable on the beat (END_MEL: B E G♯ E over E, F♯ C♯ A over F♯m). It plays a D for the somersault's landing and an A on the last downbeat.

7. The soundtrack

"Hello World" has no audio files. Its music is one song, 278.7 s long, that the engine's software synth (src/synth.c) computes while the show runs. It's 144 bars at 124 BPM, in four movements and a coda: one movement for Scene 1, two for Scene 2, one for Scene 3, and the coda under the end card. This section covers the synth and its voices, the song section by section, how the picture keeps time with it, and how to change the music for a demo of your own.

Bars count from 0 at the start of the show. A bar of 4/4 at 124 BPM lasts 240 / 124 = 1.935 s, so bar n starts at n × 1.935 s. A range such as "bars 26-44" starts on the downbeat of bar 26 and stops on the downbeat of bar 44, so it's 18 bars long. Timing covers the clocks the picture runs on and how they meet these bars.

7.1. The softsynth (src/synth.c)

The synth uses no samples, and every sound is computed. A sequencer steps through the song in sixteenth notes, accurate to the sample, and reads compact one-bar patterns and chord tables. An arrangement on top sets each instrument's level, the filter and the reverb send for every section, and ramps them across it.

mc_synth_make allocates everything up front, about 650 KB. mc_synth_render then fills a buffer with interleaved stereo floats and moves the song on. It doesn't allocate, lock or do I/O, so it's safe on the audio thread that mc_audio_start starts. The output depends only on the seed: the same seed renders the same song to the bit, however the frames are split into blocks.

The seed sets the pad's start phases and detune and starts the synth's noise generator. So it moves only incidental detail, such as the hats' grain, which birds call and when, and where the kalimba's notes sit in the stereo field. The notes, the structure and the timing never change. At the end the song doesn't loop. The sequencer holds its last step and the arrangement its last values, so whatever is still sounding rings out (see How it ends).

7.1.1. The voices

They're built from a few standard parts: band-limited (polyBLEP) saw oscillators, ADSR envelopes with exponential decays, and a Zavalishin state-variable filter, which gives low-, band- and high-pass at once.

  • The kick is a sine whose pitch falls from 150 Hz to 48 Hz, with a 4 ms click of noise on top. In the jungle it starts at 95 Hz with a softer click, which makes it a frame drum.
  • The hi-hats are noise through a 9 kHz high-pass, closed (40 ms) or open (180 ms), with velocities from the pattern and a slight alternation left and right. In the jungle the same voice is a shaker.
  • The bass is a saw through a resonant low-pass. An envelope snaps it open on every note, from 130 Hz up to 2.7 kHz, and closes it again with a 90 ms decay. The finale and the jungle add a sub, a sine an octave under the bass.
  • The lead is three detuned saws through a low-pass: a pluck in Scene 1 and a slow bell in the descent. Hook B and the coda add a second lead an octave up. Both feed a ping-pong delay.
  • The pad is the chord's three notes, each on two detuned saws, the left one flat and the right one sharp, through a resonant low-pass on each side. Its cutoff is the arrangement's main sweep.
  • The finale adds a snare, band-passed noise over a 190 Hz thump, and a supersaw: seven saws detuned by up to 1.9% and spread across the stereo field. The supersaw goes through a low-pass at the pad's cutoff, so it opens and closes with the arrangement. Even at 6 kHz its aliases stay about 55 dB under its harmonics.
  • The jungle adds a six-tine kalimba. Each tine is two damped resonators run as rotating phasors: the note, and a fast-fading 6th partial. A new note takes the quietest tine and is added onto what it still holds, so nothing clicks. The kalimba feeds the delay too.
  • The jungle also has bird calls, claves, cicadas and wind. A bird is a gliding FM sine with a soft attack, and it whistles, trills or gives a macaw's falling squawk. Squawks are likelier when the birds are busiest. The claves are a 2.55 kHz sine that dies within about 30 ms.
  • The cicadas are band-passed noise buzzing at about 87 Hz, swelling separately left and right. The wind is noise band-passed between 500 Hz and 1.9 kHz as it gusts.
  • The beach adds the surf. On each side, low-passed noise rushes up and draws back on a wave of its own, every 7.3 s on the left and 8.9 s on the right.
  • The tyrannosaurus has footfalls and breath. A footfall is a sub sine falling from 95 to 36 Hz under a crunch of litter. Its breath is two longer, band-passed snorts centred at 750 Hz and a growl: a 48 Hz saw and noise through a 220 Hz low-pass, with a rasping 23 Hz tremolo. The growl rings out with an approximately 1.35 s exponential decay.
  • The footfalls and the breath draw on a noise generator of their own, so they don't reshuffle the birds and the wind.
  • A noise riser, band-passed noise whose centre climbs with its level, leads into the drops and makes the reverse swells.

The leads and the supersaw rest through the jungle, from bar 80 until the end card at bar 139, which keeps the jungle cheaper to compute than the dance tune.

7.1.2. The delay and the reverb

The delay is a stereo ping-pong of three sixteenths, a dotted eighth (0.36 s), with a feedback of 0.42. The dry signal goes in on the left and the echoes cross from side to side.

The reverb is a stereo feedback delay network. On the way in, the send loses its lows below about 150 Hz, so the tail doesn't boom, and two of Freeverb's allpasses smear its attacks. Then come eight delay lines of 32-61 ms, all prime lengths. Each line is read at its end through a one-pole low-pass, standing in for the air's damping so the highs die first, and through a gain.

An 8x8 Hadamard matrix, done as three butterfly passes and scaled by 1/√8, mixes the eight outputs. It's orthogonal, so the mixing loses no energy, and every line feeds every other. The mix is written back with the send added. Each line's gain is 10^(-3 len / (rate × rt)), so all eight fall 60 dB in the same rt whatever their length.

Each length also wanders by up to 6 samples either way on a slow sine of its own, a fraction of a cent of pitch, and is read with linear interpolation. That keeps the tail from ringing metallic. The even lines sum to the left and the odd ones to the right. That gives two different mixes of one tail, so a mono send comes out as decorrelated stereo. The shortest line (32 ms) doubles as the pre-delay.

Each movement voices the reverb its own way (RVV), and the gains and the wander are updated every 64 samples. The damping takes the highs out faster than the rt, so the tails measure shorter (T20 from an impulse, by Schroeder's backward integration):

Movement rt Damped from T20
Scene 1 and the coda 2.6 s 8 kHz 2.1 s
The descent: space, the deep 4.5 s 2.2 kHz 3.0 s
The finale: the reef 3.0 s 3 kHz 2.1 s
The jungle 1.4 s 11 kHz 1.2 s

The pad, the leads, the supersaw, the kalimba and the birds go to the reverb at the section's space level (P_SPACE). So do the snare and the claves, the only drums that do. The finale's snare picks up the reef's dark tail, and more of it as the outro opens out. The kick, the hats, the bass and the ambience stay dry.

7.1.3. The arrangement and the mix

The song has 29 sections, and the arrangement is data. Every section's levels sit in one X-macro list, RAMPS, of 278 entries. An entry names a section and a parameter. It moves the parameter from a start value by some amount over a share of the section, in a straight line or along a smoothstep. A parameter that a section doesn't mention is 0 there.

The 18 parameters are the instruments' levels, the pad's cutoff, the riser, the reverb send and the depth of the sidechain pump. The list is expanded into six column arrays, one per field, because the packer codes columns of bytes better than rows of structs. It has more than 255 entries, so each section's first entry is a 16-bit index. Editing the arrangement gives the format.

The render loop works out the targets for every sample and follows them through a smoother of about 5 ms, so a level that jumps at a section boundary doesn't click.

The mix has two buses. The drums, the riser and the tyrannosaurus go to a dry one. Everything else, from the bass to the reverb's return, goes to a musical bus that a sidechain ducks on every kick. It ducks by up to 0.8 of the section's pump level and recovers over about 160 ms. Scene 1 and the coda pump lighter (up to 0.65) than the finale (0.8-0.85).

A peak limiter then catches anything over 1.1 at once and lets go over about 40 ms, so the full finale doesn't drive the last stage hard. That stage is a DC blocker and a tanh soft clip, which never goes past 0.92.

7.1.4. Loudness

The levels follow the story. The pad carries most of what sounds between 120 and 300 Hz and lies under everything, so its level sets each section's loudness floor, and the quiet sections hold it low. The intro rises from 0.26 to 0.48 over its first 40% and holds there until the bang, where the build's 0.75 takes over.

Space and the deep sit at 0.5, and the deep sinks to 0.23 at its end. The revised tyrannosaurus sequence raises the warning pad from 0.45 to 0.65 at the beach's hush, then from 0.65 to 0.9 during the encounter, with extra pressure from a filtered-noise swell and increased pad brightness before the first snort. Its dissonant bass pulses stay present until the relief.

The following EBU R128 measurements describe the earlier 64,365-byte version, before the revised whale and tyrannosaurus music; they have not been remeasured for the current score. Measured with ffmpeg on the capture soundtrack (MC_CAP_AUDIO), the sections span 9.3 LU. The quietest are the intro (-19.3 LUFS), the deep (-17.6), space (-17.0) and the tyrannosaurus (-16.0). The loudest are the take-off and the wave (-10.0) and the finale's peak and outro (-10.7).

The current full song, including the whale reveal and revised tyrannosaurus sequence, measured from the normally linked release synth before the packed build's float rounding, measures -12.0 LUFS integrated with a loudness range of 6.4 LU. Its sample peak is -1.52 dBFS (0.839473), its true peak is -1.5 dBTP and its RMS is -14.23 dBFS. The rounded release's sample peak is 0.847487. Both full-song checks remain finite and bounded; within each configuration, rendering in different block sizes produces identical samples.

7.2. The song

The four movements share the tempo and the sixteenth-note grid. Each has its own chords, patterns, envelopes and reverb, so each sounds like a different piece.

Movement Key and chords Bars Show time Picture
1: the dance tune A major: A E F♯m D (I V vi IV) 0-26 0-50.3 s Scene 1
2: the descent D minor: Dm B♭ F C (i VI III VII) 26-44 50.3-85.2 s Scene 2, from orbit to the deep
3: the finale C major: C G Am F (I V vi IV) 44-80 85.2-154.8 s Scene 2, from the clearing water to white
4: the jungle D major: Dmaj7 Bm7 Gmaj7 A add9, ends in A 80-139 154.8-269.0 s Scene 3
Coda A major, Scene 1's chords 139-144 269.0-278.7 s The end card

Each chord lasts a bar, and a progression repeats every four bars. It restarts at each section of the first and fourth movements, so each of those sections opens on its home chord. In the second and third movements it restarts only at the movement's start. From the jungle's take-off on, a small table (JHARM) picks each section's chords instead.

This table lists the 29 sections as SEC_BAR has them. The parts below say what each one plays, and The main beats in show time lists where the picture's cues fall among them.

# Section Bars Show time Movement
0 Intro 0-4 0-7.7 s 1
1 Build 4-8 7.7-15.5 s 1
2 Hook A 8-12 15.5-23.2 s 1
3 Breakdown 12-16 23.2-31.0 s 1
4 Hook B 16-23 31.0-44.5 s 1
5 Outro 23-26 44.5-50.3 s 1
6 Space 26-31 50.3-60.0 s 2
7 Dive 31-36 60.0-69.7 s 2
8 Splash 36-38 69.7-73.5 s 2
9 Deep 38-44 73.5-85.2 s 2
10 Build 44-51 85.2-98.7 s 3
11 Peak 51-77 98.7-149.0 s 3
12 Outro 77-80 149.0-154.8 s 3
13 White 80-81 154.8-156.8 s 4
14 Look round 81-89 156.8-172.3 s 4
15 Canopy 89-94 172.3-181.9 s 4
16 Descent 94-97 181.9-187.7 s 4
17 Ladybug 97-105 187.7-203.2 s 4
18 Take-off 105-106 203.2-205.2 s 4
19 Resolve 106-107 205.2-207.1 s 4
20 Glide 107-111 207.1-214.8 s 4
21 The beach's hush 111-112 214.8-216.8 s 4
22 Tyrannosaurus 112-120 216.8-232.3 s 4
23 Relief 120-122 232.3-236.1 s 4
24 The blob 122-127 236.1-245.8 s 4
25 The wave 127-129 245.8-249.7 s 4
26 The play 129-135 249.7-261.3 s 4
27 The end 135-139 261.3-269.0 s 4
28 Coda 139-144 269.0-278.7 s 1's tables

7.2.1. The first movement: Scene 1

Scene 1 is a bright dance tune in A major, 26 bars long: intro 4, build 4, hook A 4, breakdown 4, hook B 7 and outro 3. Its groove is four on the floor, with offbeat sixteenth hats, a bouncy bass and a plucked lead on an arpeggio.

  • The intro is sparse, with no kick and no bass. The pad rises from a whisper, the filter opens, the hats fade in and then the lead. A reverse swell, the riser over the intro's last 14% (about 1.1 s), leads into the bang and is cut on its first kick.
  • The build brings in the kick and the bass, eases in the sidechain pump, and ends on a riser into the hook.
  • Hook A is the full groove.
  • The breakdown is a breather with no kick or bass, while the filter sweeps slowly back up. A riser leads into hook B.
  • Hook B is the bigger reprise, with the lead doubled an octave up.
  • The outro collapses back toward the intro. The kick, the bass, the hats and the lead fade out in turn, and the filter closes from 2.6 kHz to 280 Hz.

The timeline shows what happens on screen in each section.

7.2.2. Scene 2's music: the descent and the finale

The second and third movements score Scene 2's long journey (bars 26-80). The second, 18 bars in D minor, goes with the fall from orbit, the dive and the murky deep. The third, 36 bars in C major with its anthem held long, carries the scene from the clearing water into its faster, more realistic finale.

The second movement is the same sixteenth grid stripped right back. There are no hats, a slow bell replaces the plucked lead, the bass holds long roots, and the kick plays a half-time heartbeat. Its four sections follow the picture:

  • Space (5 bars) is dark and drowned in reverb, with the pad held low at 0.5 under the bell.
  • The dive (5 bars) fades the heartbeat in and lifts the pad from 0.5 to 0.88. It starts from space's level, so the dive's first beat has no step. A riser over the last 45% leads into the splash.
  • The splash (2 bars) is the hit. The kick and the bass fall away, and the resonant filter closes from 3.2 kHz to 500 Hz.
  • The deep (6 bars) is muffled and calm, its filter under 1 kHz. The pad is back at 0.5 and sinks to 0.23 over the last 40%, and the bell fades out. A reverse swell over the last 18% (about 2.1 s) leads into the third movement as the water clears.

The third movement is the finale. It's a four-on-the-floor groove with a backbeat snare, a fat supersaw lead, a sub and heavy pads, under the strongest pump. It has three sections:

  • The build (7 bars) brings in the kick, then the hats and then the snare, over a rising sub and a riser. The pad and the supersaw share one cutoff, which goes from 1.2 to 3.6 kHz. In bar 50 a snare roll leads into the peak, eighths turning into sixteenths and swelling from 0.3 to 1.8 times a plain hit. Within that build, the whale reveal opens the music out from 88.1 to 96.8 s, strongest around 91.9-92.9 s. It raises the held pad and reverb space while easing back the supersaw, kick and hats. The C-major harmony and existing voices carry the swell.
  • The peak (26 bars) has everything in, and it carries the whole finale: the predator's stalk, the chase, the blob's transformation and its flight. The cutoff holds 4.2 kHz through the first half. Through the back half, from bar 64, it opens to 6 kHz over a riser that climbs to 0.7.
  • The outro (3 bars) resolves and tails off. Two beats of snare sixteenths lead into it in bar 76, as the riser tops out. The cutoff closes from 4.2 to 1.5 kHz and the reverb opens out. Over the last 2.5 s (from 57% of the outro) a riser swells as the picture burns out to white.

Two things happen inside the peak without being sections of their own: the stalk and the swell.

The stalk is bars 60-63 (116.1-123.9 s), from just before the predator appears to the near miss. The anthem drops to a heartbeat. The supersaw goes out, the pad's filter shuts to 500 Hz, and the sequencer plays the second movement's pattern rows. That's the half-time kick and short roots on the bass and the sub, with no hats and no snare.

The kick on bar 63's third beat, at 122.903 s, lands 5 ms after the near miss. From 123.39 s, as the blob bolts, a beat of swelling snare sixteenths plays alone, and that's the drop. On bar 64 everything comes back at once with the filter open again, and the chase gets the full anthem.

In the code the stalk is an override. For those four bars arrange() zeroes the supersaw and sets the cutoff, and on_step picks the second movement's patterns, while the chords and the reverb stay the finale's. A section of its own would split the peak in three. It would also shift every section number that the arrangement and the jungle index by, and move the peak's ramps, which run in percent of the peak.

The swell runs over the peak's last quarter, from bar 71 (137.5 s, 2.2 s before the blob takes off) to the outro's downbeat (149.0 s). The supersaw itself swells from 1.0 to 1.4, so the flight lifts on the anthem. It's there because the opening filter alone makes the supersaw brighter rather than louder. On the downbeat the supersaw is back at 1.0, the riser cuts, and the cutoff drops from 6 to 4.2 kHz.

Scene 2 shows the picture, and Scene 2's story clock how its cues meet these bars.

7.2.3. Scene 3's music: the jungle and the beach

The fourth movement (bars 80-139) is a warm piece in D major. Open, rootless sevenths (Dmaj7 / Bm7 / Gmaj7 / A add9) sit over a deep, ringing bass, and a kalimba plays a syncopated line through the chords. The groove is hand percussion: the kick becomes a soft frame drum, the hats a shaker, and the snare's row claves in a 3-2 son clave.

Cicadas, wind and bird calls fill the forest, and the surf the beach. The music darkens for the tyrannosaurus, lifts into A major for the blob's return, and ends on a IV-V7-I cadence. Its 15 sections:

  • The white (1 bar, BARS_WHITE) is weightless. The finale's riser dies away, the pad opens, and high, bell-like tines ring long. Wind and cicadas fade in, so we hear the forest before we see it.
  • The look round (8 bars) begins the forest at BAR_WOODS (bar 81), which is Scene 3's S3_J0. The filter settles warm, the cicadas swell and the first birds call. The bass, the shaker, the frame drum and the claves come in one by one.
  • The canopy (5 bars) brightens, with the birds overhead at their busiest: whistles, trills and macaw squawks. In the bar before it, the frame drum and the claves answer each other in a fill.
  • The descent (3 bars) thins the groove out.
  • The ladybug (8 bars) leaves the kalimba almost alone, an octave up, over a soft pad, the insects and a few birds.
  • The take-off (1 bar) lifts everything on the dominant, A add9, under a riser.
  • The resolve (1 bar) comes home to D as the ladybug vanishes into the light, and the groove starts back.
  • The glide (4 bars) travels with the shaker, the frame drum and the kalimba in eighths, and the drums drop away toward the forest's edge.
  • The beach's hush (1 bar), from jungle second 50.3 at the first sight of the sea, begins the warning: a low D-E♭-A♭ cluster replaces the airy chord, and the melody, birds and percussion fall away.
  • The tyrannosaurus (8 bars) keeps the forest quiet beneath a stronger held dissonant drone. D/E♭ bass and sub pulses sound every two beats, tightening to every beat from jungle second 55.2 to 59.0 as the head turns. Pad brightness, level and a filtered-noise swell rise into the first snort. The dark harmony and pulse hold through the departure and the wait, until the relief at jungle second 67.7.
  • The relief (2 bars) brings the forest and the waves back up on A add9, the pivot into A major, with the kalimba fading back after the tension eases.
  • The blob (5 bars) comes ashore to Scene 1's hook. Its arpeggio and its A-E-F♯m-D progression are now on the kalimba, over the jungle's hand percussion and the surf.
  • The wave (2 bars) holds on A while the blob waves, with a rising run of high kalimba notes each bar and the pad bright.
  • The play (6 bars) comes in on a frame-drum-and-claves fill like the canopy's. It brings the groove back up and the sea louder, under Scene 1's hook.
  • The end (4 bars) closes on D-E7-A, held on A, as the blob sits and the last wave laps at it. The pad, the kalimba, the waves and the forest fade out with the picture, and a short swell leads into the end card.

Some sounds land on the picture's own beats:

  • Thirteen footfalls fall two beats apart from the tyrannosaurus's first bar (bar 112, 216.8 s), matching S3_REX and S3_STEP. They're heaviest as it passes closest, on the fifth and sixth.
  • It snorts twice as it looks at the camera, peaking at 57.93 and 58.54 jungle seconds (222.45 and 223.06 s). The stronger throat growl follows as it turns away, from 58.9 jungle seconds (223.42 s), and decays through the departure.
  • A soft, low pluck sounds under the blob's hops, 0.75 into the beat each one lands on (0.36 s), from bar 123 (238.4 s). All 25 hops that land on a beat get one. The jump into the swash lands between beats and gets none.
  • The plucks come from one 64-bit mask of beats, which mirrors s3_blob.c's JO_RUNS.
  • Each landing in the water also splashes, a hiss through the tyrannosaurus's breath filter. The seven splashes sit in a short table (SPLASH) of steps counted from bar 129, and the shiver on the dry sand gets none.
  • The jump's splash starts at 93.15 jungle seconds (257.66 s), the moment the picture's blob lands.

The synth counts the jungle's sections, footfalls, hops and splashes in the song's bars after BAR_WOODS. scene3.c counts its cues in bars of a stretched story, which runs four bars behind from the hush on. So the footfalls, 31 bars after BAR_WOODS, meet S3_REX at 27 story bars, and the first hop's pluck, 42 bars after it, meets JO_HOP0 at 38. Scene 3's jungle clock has the stretch, and Scene 3 the picture.

7.2.4. The coda under the end card

The last five bars (139-144, 269.0-278.7 s) play Scene 1's movement again, under the end card. Its kick, hats and bass return, with the plucked lead and its octave layer on the arpeggio, over A-E-F♯m-D, and the last bar goes home to A. A reverse swell over the jungle's last 12% (about 0.9 s) leads in, and the coda comes straight in on the first kick.

On top, the kalimba sings the card's words as the blob lands on them, one note a syllable on beats 4-10: B E G♯ E F♯ C♯ A for HEL, LO, MI, CRO, COS, MIC and WORLD (END_MEL, in step with main.c's END_LAND). It plays a D for the somersault's landing on beat 14 and an A on the last downbeat, beat 16. Over the last bar everything dies away with the picture. The end card has the picture.

7.3. Changing the music

There's one song, in src/synth.c, and it belongs to the engine, not to the demo. The Makefile links src/synth.c and src/audio.c into every demo under demos/. So a demo of your own plays "Hello World"'s song until you change that file, and demos that share a tree share the song.

You can edit the song's tables and keep the synth's voices and effects, or replace the file with a synth of your own. Either way the picture has to be timed to the new song.

7.3.1. How the picture reads the music

The picture never looks at the synth's state: no note events, no levels. It reads one number, the playback position, and times everything from it. Each frame, main.c takes its clock from mc_audio_time(), the seconds of the song that have been heard, so bar n of the song is at n × 240 / BPM seconds of the show. The master clock explains how the position is estimated and what happens without audio.

So everything that should land on the music is a time in bars. main.c defines BPM (124) and BAR_S, one bar of 240 / BPM seconds, and writes its cues as multiples of BAR_S. T_BANG, the first kick of the build, is 4 * BAR_S, and the scenes are 26, 54, 59 and 5 bars long.

For motion that follows the beat, mc_scene.h has the timeline helpers. Fill an mc_timeline with the time, the length and the tempo, then:

  • mc_beats() gives the time in beats.
  • mc_beat_pulse() gives a 0..1 pulse that jumps to 1 on each beat and decays, faster for a higher sharpness.
  • mc_window_active() and mc_window_phase() switch a span of time on and give the 0..1 progress through it.

"Hello World" uses a smooth sine of mc_beats() for Scene 1's pulsing, because a hard pulse makes the motion stutter. It uses mc_window_phase() for fades such as Scene 1's collapse.

mc_timeline tl = { t, DUR, BPM };                               /* main.c's clock */
float sb = 0.5f + 0.5f * sinf((float)mc_beats(&tl) * MC_TAU);   /* a smooth beat */
if (mc_window_active(&tl, 4 * BAR_S, 4 * BAR_S)) { /* the build, bars 4-8 */ }

Cameras, tracks and beats describes the helpers, and the toolkit reference lists their calls.

7.3.2. Where the song is written down

Everything is in src/synth.c, most of it near the top:

What Where What it holds
The tempo SONG_BPM 124. The grid is sixteenths, STEPS_PER_BAR 16 to a bar of 4/4.
Section lengths BARS_* Bars per section. BAR_* work out the starts and TOTAL_BARS the length.
The section table SEC_BAR[], SEC_COUNT Each section's first bar, 29 sections, closed by TOTAL_BARS.
Chords PROG[6][4] A root (a MIDI note) and three offsets per chord, four chords to a row.
Patterns PAT[4][6][16] One bar per movement: kick, hat velocity (0-7), open hat, snare, bass, arpeggio.
Envelopes ENV_BASS, ENV_LEAD, ENV_BFILT Attack, decay, sustain and release, in seconds, per movement.
The arrangement RAMPS Every section's levels and sweeps (below).
The reverb RVV The rt and the damping per movement.
The jungle's kalimba KAL4, JHARM Its steps, octave and ring per section, and the chords from the take-off on.
Fills FILL, in on_step Extra kick and snare hits in five bars, as 16-bit step masks.
One-off events on_step The stalk (BAR_STALK), the footfalls, the snorts and growl, the hop mask, SPLASH and END_MEL.

The bass row holds semitones above the chord's root an octave down, and REST (127) means no note. The arpeggio row indexes the chord's three notes over two octaves (0-5), from an octave above the root. The movement's lead plays it: the pluck, the bell, the supersaw or the kalimba.

on_step picks the movement from the section number. Sections 0-5 and 28 play the first movement's tables, 6-9 the second's, 10-12 the third's and 13-27 the jungle's. The movement chooses the PAT and PROG rows, the envelopes, the reverb's voicing and the voice that plays the arpeggio.

A few other places count sections by number too: where each progression restarts, KAL4[sec - 13], JHARM[sec - 18], and sec > 24= for the kalimba playing Scene 1's arpeggio. Changing notes and levels inside the 29 sections touches none of this. Adding or removing a section means changing SEC_COUNT, renumbering RAMPS and moving these tests.

Two more things to know. In synth.c the S3_ and SCENE3 names (BARS_S3_BUILD, BAR_SCENE3 and the rest) belong to the third movement, Scene 2's finale, not to Scene 3. The jungle's are BARS_JUNGLE, BAR_JUNGLE and BAR_WOODS. And SEC_BAR and FILL hold bars in a byte, so a song can be at most 255 bars long, about 8 minutes at 124 BPM.

For a demo of your own, start with the events tied to "Hello World"'s picture, which you'll want to delete or retime. They're the stalk, the fills, the footfalls, the snorts and the growl, the hop plucks, the splashes and the coda's melody.

7.3.3. Editing the arrangement

Two entries from RAMPS, one from the intro and one from hook A:

F(0, P_HAT|SM, 40, 60, 0, 450)   /* intro: the hats fade in from 0 to 0.45 over its last 60% */
LEVEL(F, 2, P_KICK, 1000)        /* hook A: the kick at full level throughout */

F(section, parameter, a, w, v0, d) moves the parameter from v0 to v0 + d while the section's progress runs from a% to (a + w)%, and holds it before and after. |SM makes the move a smoothstep instead of a straight line. LEVEL(F, section, parameter, v) holds v for the whole section. Levels are in thousandths, 1000 being the instrument's full level, and P_CUT is in Hz.

The parameters are P_KICK, P_HAT, P_BASS, P_SUB, P_LEAD, P_LEAD2 (the octave layer), P_PAD, P_CUT (the cutoff of the pad and the supersaw), P_SUPER, P_SNARE and P_PUMP (the sidechain's depth). Then come P_SPACE (the reverb send), P_RISER, P_KAL, P_BIRD, P_AMB (the cicadas), P_WIND and P_SURF. In the jungle, P_KICK is the frame drum, P_HAT the shaker and P_SNARE the claves.

Some rules come from the way the list is stored:

  • A parameter the section doesn't name is 0 in it. Each section has to list every instrument it wants to hear, and the cutoff too.
  • Keep the entries in section order, and give every section at least one. mc_synth_make finds each section's first entry by scanning the list, and arrange() reads from there up to the next section's first.
  • v0 and d are each stored in a signed byte. Levels are kept in hundredths, so they have to be multiples of 10 from -1280 to 1270.
  • The cutoff is kept in steps of 50 Hz, so v0 and d must each stay within 6.35 kHz either way, though v0 + d can go higher. Nothing warns when a value doesn't fit: the cast wraps it round.
  • A ramp whose d isn't 0 needs a w above 0.

7.3.4. What has to stay in step with the timeline

synth.c and main.c each write down the tempo and the song's layout, and nothing checks one against the other. When they disagree, nothing fails: the picture just lands off the music. These have to match:

  • The tempo: SONG_BPM in synth.c and BPM in main.c, 124 in "Hello World".
  • The scenes' lengths. main.c's SCENE1_DUR to SCENE4_DUR (26, 54, 59 and 5 bars) go with the synth's BAR_SCENE2, BARS_SCENE2 + BARS_SCENE3, BARS_JUNGLE and BARS_END, so TOTAL_DUR matches TOTAL_BARS.
  • Scene 1's cues, T_BANG to T_OUTRO, and the synth's BAR_BUILD to BAR_OUTRO.
  • The stretch tables, S2_SEG in main.c and S3_SEG in scene3.c, which change pace at the song's section changes. If a section changes length, the stretch has to change with it, or the cues after it slide.
  • The events timed to the picture: the stalk and the predator's cues, the footfalls and S3_REX, the hop mask and SPLASH and JO_RUNS, and END_MEL and END_LAND.

Defined twice has the full table, and the times that the jungle's shaders carry as literals.

The song's length matters too. The release build exits when the clock reaches TOTAL_DUR, and the capture build writes that much audio and silences the rest. A song longer than the show is cut off there. A shorter one holds its last step and rings out while the picture goes on.

A tempo that differs shows as drift. At 1 BPM off, the show's last bar lands more than 2 s away from the music's.

A demo of your own can ask the synth for its tempo instead of keeping a second copy. mc_synth_bpm() returns SONG_BPM, and mc_synth_length() the song's length in seconds. The tutorial fills its mc_timeline from them. "Hello World" doesn't, because its cue times are compile-time constants.

7.3.5. Replacing the synth

Any src/synth.c that defines the five functions of mc_audio.h will do: mc_synth_make, mc_synth_free, mc_synth_render, mc_synth_bpm and mc_synth_length. A few rules carry over from this one:

  • mc_synth_render runs on the audio thread, so it mustn't allocate, lock or do I/O. Allocate everything in mc_synth_make.
  • It writes interleaved stereo floats at 44.1 kHz (MC_SAMPLE_RATE), each one finite and between -1 and 1.
  • It has to render the same samples however the frames are split into blocks. The audio thread asks for 1024 frames at a time and the capture build for
  • tools/checks/synth.c tests the last two rules over the whole song, and python3 tools/verify.py builds and runs it.
  • In the packed build, library calls go through the hashed import table in include/microcosm/mc_imports.h. For maths it lists sinf, cosf, tanf, tanhf, expf, exp2f, powf, floorf, ceilf, acosf and atan2f, with calloc and malloc for memory.
  • Add any other library function you call to that list, or the payload imports it by name, which costs more bytes. Include mc_imports.h after the system headers. The pipeline explains the table.

7.3.6. What the synth costs

In the 64,365-byte version, before the current size reductions, synth.c took about 4.5 KB of its 61,026-byte stream in this tree's build, as codecost.py counts it. That's about 3.4 KB of code and 1.1 KB of tables. The biggest pieces are mc_synth_render (1.65 KB), on_step (0.62 KB) and the arrangement.

The arrangement's 278 ramps take 0.71 KB in their six columns, about 2.6 bytes each. The patterns take 88 bytes and the chords 40. audio.c adds another 0.44 KB. The synth's float constants sit in the shared constant pool and aren't counted per file. The current packed file has 2,299 bytes to spare.

In memory it takes 661,344 bytes, allocated once. Almost all of that is the delay's two rings of 65,536 samples (512 KB) and the reverb's eight lines (128 KB).

In time, the capture build renders the whole song in 8 to 10 s on the i7-8550U laptop, about 30 times real time. Playing it live takes about 3% of one core.

8. Engineering notes

These notes apply across the scenes. The renderer they lean on, from the TAA to the warm-up at load, is described in The renderer and its post stack.

  • Depth is cleared with the mask on. mc_renderer_begin sets glDepthMask(GL_TRUE) before its clear, because with the mask off a depth clear silently does nothing and each frame would inherit the last one's depth. mc_renderer_end turns the mask back on at the end.
  • Instance buffers are sized up front. demo_make reserves NBODY + MAX_LOGO instances on every shape's GPU buffer (mc_mesh_reserve_instances) and allocates CPU instance arrays to match, so nothing reallocates mid-animation. The rounded box draws both the orbiting bodies and the logo cubes, which is why every buffer is sized for both. Scene 2's props, fish and creatures are reserved for their own budgets in the same place.
  • Scene 1 and the end card draw their sky first, as a full-screen pass with the depth test and depth writes off. The grid is drawn additively, depth-tested but with depth writes off, so the bodies hide the grid and the grid can't hide them (see The sky and the grid).
  • The supersampling is set per scene. The windowed main renders the cheap frames, Scene 1 and the end card, at 2160 lines high, and Scenes 2 and 3 at 1620: 200% and 150% of a 1080p picture (demo_fit). It switches on the near-black cuts into Scene 2 and into the end card, where the TAA's history starting over can't show. The rule is set in lines rather than as a percentage, so a 1440p desktop gets 150% and 112%. A 4K one gets 100% and 75%, the same frames as at 1080p instead of four times the work. A picture under 1080 lines counts as 1080, and the scale stops at 200%.

    MC_RENDER_SCALE=25..200 sets one percentage for the whole show instead, and 80 suits an integrated GPU. The headless screenshot main and tools/verify.py keep one scale throughout (80%, or --scale), and so does the capture main (200%, or MC_CAP_SCALE). The jitter depends only on the frame's time, so a frame rendered on its own is always the same plain frame.

  • The release is set up the way a compo screen wants it. It opens fullscreen at the desktop's resolution with a blank pointer, and the screen is black from the moment the window opens (see The platform seam). The picture stays 16:9 on any screen, because every shot is composed for that shape: in Scene 3, for one, two trunks frame the beach. Each frame the renderer is sized to the largest 16:9 rectangle in the window, centred between black bars. The load's last step, demo_warm, draws eight of the show's frames that are never shown, so that no program has to be built, or rebuilt for a new kind of target, during the show. The dev build stays a 1280x720 window, and letterboxes too if a window manager tiles it to another shape.
  • Historical frame costs, before the current size reductions. These were measured on 28 October 2026 with tools/verify.py --profile --width 1920 --height 1080 --frames 16 on the reference laptop's Intel UHD 620. Each is the median wall time of 16 frames at that moment, finished with glFinish. The middle column is the 80% that suits an integrated GPU, and the last is the release's own scale at 1080p.

    Moment Show time At 80% Release scale
    Scene 1, the breakdown 30.0 s 15.6 ms 62.2 ms (200%)
    Scene 2, just under the surface 73.3 s 20.9 ms 56.3 ms (150%)
    the deep 104.0 s 25.8 ms 66.9 ms
    the chase 126.1 s 35.3 ms 78.3 ms
    the flight 149.5 s 34.2 ms 80.1 ms
    Scene 3, the look round 163.6 s 40.9 ms 102.7 ms (150%)
    the canopy 177.3 s 33.2 ms 85.9 ms
    the ladybug 192.3 s 30.7 ms 91.8 ms
    the tyrannosaurus 220.3 s 35.9 ms 98.9 ms
    the blob ashore 242.1 s 40.4 ms 112.5 ms
    the blob at play, spray in the air 257.8 s 42.2 ms 122.1 ms

    Scene 1 at 200% costs about what Scene 2's cheapest sampled frame costs at 150%, which is why it can have the higher scale. The release's scale is meant for a party machine's discrete GPU. One 25-40 times as fast as the UHD 620, such as an RTX 5080, should draw the worst of these frames in 3-5 ms of the 16.7 ms a frame has at 60 Hz. That was an extrapolation for a target competition GPU, not a measurement on an RTX 5080; the recording was made on the UHD 620. The revised visuals need fresh profiling. MC_RENDER_SCALE=80 is there for a GPU that can't sustain the default scale.

  • Several guards keep a still picture still, and the jungle's foliage needs them most. The bright pass drops any pixel that isn't finite, so a stray NaN stays one pixel. The TAA checks last frame's depths over 4x4 texels before it lets its history go, so an edge whose depth only flips under the jitter keeps it. The grain is added in the display's steps, where it can't make the shadows boil. The ambient occlusion rebuilds its normal from the depth texels beside the pixel, so it draws no dashes along silhouettes. Contact shadows fade out far from the near plane and under the depth of field's blur. The motion blur skips a velocity that isn't a number and takes at most 16 taps a side. The palm leaflets thin out by halves as they shrink (see The trees).
  • Fades are dithered. The grain fades out with the picture, so the present step adds its own triangular dither of one step either way to the 8-bit write, whatever the fade. The ldr target before the TAA holds half floats, so the history never stores 8-bit steps for the dither to break up. The dither shrinks to nothing within a step of pure black and pure white. So the black between the scenes and at the end, and the burn-out's white, stay at 0 and 255.
  • There's one value noise. The shaders' value noise is a single helper, spelled the same in every file so that genshaders.py stores it once. vn3(p) is one fetch from the 32³ volume of random bytes that noise_make fills first thing at load (unit 1's 3D slot, wrapping, trilinear). Its coordinate is eased through the cubic inside its cell, so the filter leaves no creases. In 2D it reads the volume's first layer (vn3(vec3(p, 0.0))), where the fetch lands on texel centres in depth and the blend is plain bilinear.

    The sampler blends with 8 bits of sub-texel weight, so a value moves in steps of 1/256 of its cell's corner difference. That's fine enough for every use that sharpens it, such as the blob's normals (differenced across a sixth of a cell), the coral's and the weed's relief, and the sea's foam edges. It's too coarse, though, to difference into the slope that a mirror-sharp sun follows. So the sand's vnd, which gives its value and its exact slope, keeps its hash. The beach's sea keeps its own vn too, because there the draw decides which waves break white beside the blob as it plays.

    The volume repeats every 32 cells, which is wider than any use spans where its pattern could show. The hashes that remain either draw per cell or per instance (stars, specks, leaves, litter, the bakes' Voronoi cells) or have to tile at periods the volume can't (jt_bake.frag's pn). The seabed's rock tile is on unit 2, because a program can't read a 2D and a 3D texture from one unit.

  • Shaders hot-reload in the dev build. programs.def lists all the vertex/fragment pairs, including the shared colour and shadow dependencies and the material baker. It pulls in scene3.def, which pulls in each jungle package's s3_<pkg>.def. Content hashes are polled four times a second. Changed programs and replacement material textures are installed together, and only once the whole update has succeeded. If it fails, the working set stays, and the update is tried again when the source next changes. Every uniform the C side sets sits at a fixed layout(location), and every sampler off unit 0 at a fixed layout(binding). So a replacement program needs nothing looked up again, except that uwsurf's uSandOnly default is set again. The Makefile passes in the selected demo's shader root (MC_SHADER_ROOT). The release build embeds all the shaders and compiles out the disk watching.
  • The windowed dev build has a transport for scrubbing the timeline. SPACE pauses, LEFT and RIGHT jump 5 s back or forward, and UP doubles the playback speed and DOWN halves it (from 1/8 to 4 times). The first keypress takes the clock over from the audio, and every change logs the time, the speed and the section's name. A seek backward reseeds Scene 1's collision sim and replays it forward to the new time (sim_seek), since the sim only runs forward. All of this sits under #if !defined(MC_RELEASE) && !defined(MC_HEADLESS), so the shipped binary doesn't carry it (see The demo as a case study).

9. How it ends

The show plays once. The master clock climbs straight through all three scenes and the end card and is clamped at TOTAL_DUR, about 278.7 s. post.fade takes the end card to black over its last three beats as the coda dies away. When the clock reaches the end, the release build exits. The program ends with the show instead of holding a black frame forever, and it tears nothing down first. The OS releases the window, the context and the audio thread, and MC_TIDY keeps that cleanup out of the payload. It leaves by the exit_group system call (mc_exit) and not libc's exit, so no library's exit handler runs. That way nothing tears the GL driver or the audio library down under the audio thread, which may still be in the middle of a write.

The music rings out too. Past the end, the sequencer holds its last step and the arrangement its last values, so whatever is still sounding decays instead of the song starting over in the audio already queued. The coda's last tenth of a second is at -47.8 dB (RMS), the first tenth after the end at -47 dB, and it's down to -65 dB by 0.8 s. The dev build stops the audio once and keeps running, frozen on black, so the transport can still inspect the ending. ESC quits early in any build.