Chapter 01 / The display
How the dot grid is generated
A normal canvas hides its pixels. We made ours visible. Each point becomes one tiny lamp: position is fixed by a grid; colour and radius carry the scene.
Calculate every dot position
The vertex number tells us its column and row. Multiply both by the grid step and every point lands on an exact rhythm—no position buffer required.
Why this formula Modulo returns the column and integer division returns the row. Multiplying by step converts those grid indices into pixels. The production value is 4 px: dense enough to read as an image while keeping the dots visible.
Map brightness to dot area
A circle’s area grows with r². If radius followed light directly, mid-tones would look too dark. Taking the square root makes visible dot area track the signal instead.
Why this formula Circle area is proportional to r², so using √L makes visible area proportional to luminance L. rmax is the Dot size setting and limits the result to the current grid cell.
Render each point as a circle
WebGL points begin as squares. Inside every square we measure distance from the centre, throw away the corners, then soften only the outer 28% to stop the edge flickering.
Why this formula Remapping point coordinates to −1…1 puts the centre at zero; the dot product gives squared distance without a costly square root. Values below 0.72 stay solid, while 0.72…1 becomes the narrow anti-aliased edge.
Bench test / Halftone settings
Adjust the halftone grid
Chapter 02 / The light
How the Aurora curtain is generated
Random pixels look like television snow. Aurora needs continuity, layers and direction. We build those properties one at a time, keeping every intermediate result visible.
Generate smooth value noise
We hash the four corners of a cell, then blend between them with a smooth S-curve. The same coordinate and seed always return the same number, so the field moves without boiling.
Why this formula The cubic fade curve has zero slope at both ends, so neighbouring noise cells meet without seams. The coefficients 3 and −2 are the smallest cubic solution that satisfies those two flat-end conditions.
Sample one noise field in screen space
We sample one octave directly in screen space. The result is coherent, but it still looks painted onto glass.
Why this formula One deterministic noise lookup is the simplest coherent field. The same position p and seed always return the same value, which prevents random flicker between frames.
Project the noise field toward the horizon
Move the slider from flat screen coordinates to reciprocal depth. Near the horizon, a tiny vertical move travels a huge distance across the noise plane.
Why this formula Perspective makes apparent size inversely proportional to distance. e is elevation, s is observer-dependent plane scale and p prevents division by zero. mix(a, b, λ) is exactly (1 − λ)a + λb: 0 uses flat coordinates and 1 uses full perspective.
Combine four noise octaves
Each octave doubles frequency and halves amplitude. Large forms survive; small structure appears.
Why this formula The factor 2.05 adds detail at roughly double frequency without producing obvious repetition. Amplitude 0.5 gives every smaller octave half the influence. Four octaves were the best detail-to-cost balance; N normalizes the result back to 0…1.
Distort the sampling coordinates
We use a slow noise field to push the x-coordinate before sampling the fast field.
Why this formula F is the fBM function from the previous step. Frequencies 0.35 and 0.4 create broad bends, while 0.9 and 0.28 stretch the final field vertically. Subtracting 0.5 centres the offset and multiplying by 2 restores a symmetric −1…1 range.
Extract narrow light ribbons
smoothstep selects the bright bands; a power curve tightens or opens their edges.
Why this formula l and h are the low and high thresholds that define which part of the noise becomes light. Smoothstep avoids hard pixel edges. Raising the mask to c, the Contrast setting, suppresses mid-values and leaves narrow bright ribbons.
Map elevation to colour
Lower and upper hues blend by elevation. Another moving noise field makes the colour shimmer.
Why this formula e is elevation, while h₀ and h₁ are the lower and upper hue settings. The exponent 1.15 delays the upper colour slightly. lerpHue follows the shortest path around the colour wheel; shimmer adds a small animated noise offset.
Control room / Aurora settings
Adjust all Aurora parameters
Every control below maps to the production Settings panel.
How quickly time advances through every noise field.
How fast the sampled plane travels away from the viewer.
A final multiplier: more emitted light, same geometry.
Adds a second ray field and moves the ribbon thresholds.
The two endpoints of the altitude colour ramp.
Amplitude and velocity of moving hue noise.
Colour purity and total light before clamping.
The exponent that decides whether curtains are foggy or cut sharp.
Chapter 03 / The space
How the observer and atmosphere create depth
Camera height, horizon and perspective are not cosmetic transforms. They change the coordinates fed into every field. Atmosphere then decides how much of that distant light survives.
Convert screen height into distance
Elevation becomes the denominator. Observer height changes the plane scale before the division.
Why this formula h is the horizon, hₒ is Observer height and p is Perspective. The first line maps the top of the sky to high elevation and the horizon to zero. Constants 1.15 and 0.75 move plane scale from 1.15 down to 0.40; adding p keeps horizon distance finite.
Apply haze and horizon glow
Haze falls exponentially with distance; glow is deliberately local and quadratic.
Why this formula Exponential decay is the standard Beer–Lambert model for light crossing a medium. The glow uses only the lowest 12% of the sky; squaring its band value concentrates light tightly at the horizon.
Chapter 04 / The silhouette
How the mountain ridges are generated
Each ridge is a one-dimensional fBM profile with its own frequency, amplitude and seed offset. Depth makes later ridges smaller; a narrow exponential rim keeps their edges readable.
Generate each ridge from one-dimensional fBM
Roughness controls both frequency and octave count. Height sets the amplitude in screen space.
Why this formula One-dimensional fBM already has the self-similar structure of terrain. fᵢ and offset oᵢ separate the ridge layers; aᵢ makes distant layers lower. Exponent γ moves from 1.4 to 0.9 as Roughness r increases, producing sharper peaks.
Add rim light below each ridge
An exponential is bright at the exact ridge and quickly disappears into rock.
Why this formula L₀ is base light and R is the Rim light setting. An exponential gives maximum light exactly at the silhouette and a smooth falloff below it. The 0.035H denominator keeps the rim about 3.5% of viewport height on every screen.
Control room / Mountains
Adjust the mountain profile
Chapter 05 / The reflection
How the sky is reflected in the water
Water starts by sampling the completed scene above the horizon. A nonlinear vertical map preserves the distant band; moving noise shifts each reflected row sideways and fragmented sine crests add foam.
Map each water row to a sky row
Distance from the horizon chooses a source row. Wave phase supplies the horizontal error.
Why this formula yₛ and xₛ are the sampled sky coordinates; w is wave noise, c is cell size and s is the depth scale. Mixing 58% linear depth with 42% square-root depth preserves more detail near the horizon. The sideways offset grows toward the viewer.
Generate broken foam crests
Raise it to the sixth power for thin crests, then multiply by thresholded fBM to punch gaps.
Why this formula c is the crest, n is the fBM field and m is its thresholded mask. The sixth power turns a wide sine wave into a narrow crest without a branch. Values 0.54 and 0.86 define the soft interval where natural gaps appear.
Control room / Water
Adjust reflection and waves
Chapter 06 / The second pass
How stars are distributed and animated
Stars render in a separate pass so they can sit behind the light but remain sharper than the sky. A stable hash chooses cells, magnitude sets size and colour, and atmospheric airmass dims the horizon.
Convert star count into cell probability
Multiplying by cell area keeps the apparent number of stars stable when grid density changes.
Why this formula N is the number of sky cells: viewport area W × H times horizon fraction h, divided by cell area c². Count divided by N is therefore a per-cell probability. The 0.35 cap prevents the field from becoming a solid texture.
Animate twinkle and atmospheric fading
Hashed phase and speed prevent synchronized blinking; extinction removes weak stars near the horizon.
Why this formula Base magnitude M is multiplied by twinkle T and atmospheric transmission E. Each star hashes its own phase φ and speed ω. The 0.25 floor stops complete blackouts, while 0.11 removes dim stars as airmass rises near the horizon.
Control room / Stars
Adjust the star field
Chapter 07 / The event layer
How meteor trajectories are generated
A meteor shower does not move in arbitrary directions. Each streak begins on screen and travels away from a shared point above it; speed, light and length vary only after that direction is established.
Calculate direction from the radiant
Normalize the vector from radiant to start point, then rotate it by a small random spread.
Why this formula Perspective makes shower paths appear to diverge from one radiant. Subtracting radiant position from start position gives that direction; normalization removes distance so the Speed setting can control velocity independently.
Convert trail length into lifetime
Converting requested screen length into seconds makes trails stay consistent across velocities.
Why this formula Distance equals speed multiplied by time, so time must be distance divided by speed. Trail length is stored as a fraction of viewport height H, keeping the visible streak proportional on every screen.
Control room / Meteors