Made by AI
The deck drift: a city of blocks on the dreadnought's deck, leading to the command tower
Space Travel · Interactive explainer · Scene 10

The Dreadnought

A capital ship more than two hundred units long and a hundred thousand triangles, not one of them modelled by hand. How code lofts a hull from four cross-sections, grows a city on its decks and lights it with a low, cold sun — with the real ship generator and the real shaders running right here in your browser.

The scene

Right after the fleet drops out of hyperspace, the dreadnought gets eighty beats — 38.4 seconds, the longest entry of the demo. We are a tiny craft in low orbit above a blue planet, and the camera is cut into four shots of twenty beats (9.6 s each, so every cut lands on a bar line). In the overhead pass the ship's nose slides in over the top of the frame while the planet's horizon glows below. In the planet eclipse we watch from far to the side as the hull crosses the frame and covers the planet's bright limb. In the deck drift we hang just above the decks and slip backward over a city of blocks toward the command tower. In the departure we wait beside the stern while seven engines pass and recede.

Overhead pass
Overhead pass: the nose enters over the horizon.
Planet eclipse
Planet eclipse: the hull crosses the bright limb.
Departure
Departure: seven engines recede.

The direction was "cold beauty": grey plating under a low, grazing sun; every block throwing a long shadow on the deck; blue light bouncing up from the planet onto the belly; faint reflections along the edges. And scale: the ship does the moving. In three of the four shots the camera simply hangs in space, so the hull's size is measured by how long it takes to pass — and by how much of the planet it hides.

The core trick, in plain words: nothing is modelled by hand. The large forms are lofts — a few cross-sections joined along the length. All the detail comes from layers of rules — a city, armoured rims, turrets, windows, belly plates, a tower, engine fins — that ask the hull's own shape functions where the deck is, and use a seeded random generator and noise so that nothing repeats. The result is an ordinary triangle mesh of about a hundred thousand triangles, built once at load time, then drawn every frame like any game model: lit by one shader, with shadow maps for the self-shadowing.

You don't need graphics experience to follow along. Each section introduces one concept, with a small interactive figure. Code appears in two forms: pseudo-code (plain logic) and the real code that runs in the demo.

Live: the real shaders

This is the actual scene. The ship is generated in JavaScript by a line-for-line port of DreadnoughtModel.cpp and the mesh helpers it uses (same seed, same Mersenne Twister generator, same noise), then drawn with the demo's own shaders: the planet and sky (sky.frag), the depth-only shadow pass into two cascades fitted exactly as in the C++, and the hull (mesh.vert + hull.frag with lighting.glsl). The shots, the ship's motion and the sun come from a JS mirror of render(). Drag time to scrub the whole 38.4 s, or hold one shot.

Try: hold shot 2 (deck drift), pause, and switch shadows off — the city loses all its depth. Switch off baked occlusion and watch the foot of every block. Raise the sun elevation to 60°: the shadows shrink and the hull goes flat; the low sun is what draws the relief. Hold shot 0 and drag planet bounce to 0: the belly turns black. Toggle the detail layers one by one — with only big forms left you see the five lofts, the reactor bulb and the engine block.
Differences from the demo: WebGL2 has no storage buffers, so mesh.vert and shadow.vert read the ship's model matrix from a uniform; the Poisson array gets an explicit size, the shadow sampler a precision qualifier; WebGL has no clamp-to-border, so taps past a cascade's edge clamp instead of reading "lit". The demo's post chain (bloom, chromatic aberration, vignette, grain) is replaced by ACES tone mapping and a glow with a hard threshold of 2.5 on blurred samples. The demo uses a soft knee, which starts admitting light above a brightness of 1.25 at this entry's threshold of 2.5. Where C++ leaves the order of three random calls unspecified (underside plate sizes), the port takes them left to right, so a few belly plates may differ. The sky's stars are sized for the demo's 1080-line frame rather than this canvas' pixels, so they are not fattened by the smaller resolution.

Background

Nine ideas: the first five build the ship, the last four light it and film it.

1 · Lofting a hull

In shipyards, lofting was the craft of drawing a hull's lines at full size on the floor of a large loft, cross-section by cross-section, so the frames could be cut to shape. Computer graphics borrowed the word: a loft is a surface swept through a series of cross-sections. The demo's makeLoft is the simplest possible version: one polygon, scaled and shifted at each ring along the z axis; consecutive rings are joined by quads (two triangles each) and the two ends are closed by triangle fans.

The polygon is a regular polygon rotated half a step — vertex i sits at angle (i + ½) · 360° / sides — and then stretched to fill the square [−1, 1]². With four sides that is exactly a square, so every box in the model is just a 4-sided loft with two rings. With six sides it is a hexagon with a point at the top and at the bottom and two short vertical walls: a ridged deck, a keel, and side walls for windows.

The hull is one 6-sided loft through four rings (z, half width, half height): (−100, 55, 9), (−20, 33, 6.5), (60, 11, 2.8), (100, 0.6, 0.4) — a dagger two hundred units long, nose along +z. Three stepped tiers sit on it, 4-sided lofts that follow the ridge with lifts of 2.5, 7 and 11.5 units and taper to a half width of 2 units at their front ends (z = 4, −12, −28). A reactor bulb hangs underneath, the engine block closes the stern, and the command tower stands on the top tier.

Drag in the right-hand views to move the probe along the ship. Left: the cross-section at the probe — the hull polygon (the faint outline is the regular polygon before stretching), the tiers, and in gold the topSurface the city will build on (§2). Right, top: plan of one half (the ship is symmetric). Right, bottom: side elevation. Dashed lines mark the four hull rings. Change the number of sides: 4 gives the box, 6 the hull.

The same numbers are also kept as functions: ridgeHeight(z) and hullHalfWidth(z) interpolate linearly between the rings, tierTop(tier, z) follows the ridge. Every detail layer that follows uses them to ask "how wide is the ship here, and how high is the deck?"

One more step turns lofts into hard metal: flattened() gives every triangle its own three vertices, all carrying the triangle's face normal. Lighting is then constant across each flat face and changes abruptly at edges — the crisp, faceted look of machined plates. The domes are the exception: smooth icospheres whose normals vary across the surface.

2 · A city that reads the deck

The decks are covered by a dense "city" of boxes, generated by walking a grid of 1.4-unit cells over the whole ship. The city needs to know where the deck is, and that is topSurface(x, z): on the hull, the ridge sloping down toward the walls, ridge(z) · (1 − ½·|x| / w); on a tier, its flat top; where both exist, the higher wins; off the ship, "nothing" (NaN, "not a number").

For each cell the generator rolls dice, in this order:

The real city, seen from above (nose to the right): every block placed by the port of addCity, brighter = taller, towers outlined in gold. Hover to inspect a block or a spot on the deck. Turn on the rejected footprints: they line up along the tier edges and the hull's rim, exactly where a block would have hung over a step.

3 · Variation without repetition

A big ship built from rules has one enemy: repetition. The eye spots a regular pattern instantly, and a regular pattern reads as small — a comb, a grille, a toy. The model fights it with two kinds of deterministic "randomness":

A seeded random generator. The model creates one std::mt19937 — the Mersenne Twister, published by Makoto Matsumoto and Takuji Nishimura in 1998 — seeded with 20261017. Each call returns the next number of a long sequence that looks random but is fully determined by the seed, so the demo builds the same ship on every run (and this page can rebuild it). The catch: the city, the rims and the belly plates all draw from the same stream, in order. Change how many numbers the city consumes and every rim after it changes too.

Noise. Where a rule needs coherent variation — neighbours that resemble each other — it reads value noise instead (the same idea as the noise in the warp explainer: random values on a grid, smoothly blended, several octaves summed). The city's districts are 3-octave noise at a scale of 0.045 per unit, so they are tens of units across. Windows are lit in runs: a window exists where 2-octave noise along the hull, fbm(z · 0.12, row · 3.1), is above 0.5, so lit stretches alternate with dark ones instead of a uniform grid of lights.

Rules also vary at several scales at once. The armoured rims along every edge are segments of random length (1.2–4 units on the hull, 1–3.5 on the tiers), random height, with a small gap between segments and about one segment in twelve missing. Plates in the shader (§4) vary per region, per plate and per half-plate. Nothing lines up with anything else for long.

Top: a hull edge with regular rim segments (a comb) and with the model's rule (random lengths, heights, gaps). The seed slider restarts a standalone generator — the ship's real rims come after the city in the shared sequence. Middle: the starboard hull wall, stern on the left, with every window position of addWalls; lit where the noise is above the threshold; dark slots are the hangar openings. Bottom: the noise along the row under the pointer (hover the wall) and the threshold.

4 · Plates, seams and bumps

The geometry stops at about a unit; everything finer is painted by hull.frag, per pixel. First it needs 2D coordinates on the surface. It uses planar mapping: whichever axis the face's normal points along most, drop that coordinate — a deck (normal up) uses (x, z), a side wall (z, y), an end face (x, y). On a model made of boxes, that is all it takes.

Then panel(uv) cuts the surface into plates, in three layers of variation. The plane is divided into 28-unit regions; a hash of the region picks the plate size (6 × 3 units × the panel scale × a factor between 0.55 and 1.6) and, 40 % of the time, turns the plates the other way. Inside a region the plates form a grid; a hash of each plate splits 35 % of them in half one way and 25 % the other way. The function returns a random value for the plate and the distance to its nearest seam.

The plate value sets the albedo (0.7–1.15 × a cold grey) and the roughness (0.35–0.7), so neighbouring plates catch the light differently. Near a seam the albedo darkens and the surface "height" drops a little; blocks(uv) adds small raised rectangles on one cell in five (the source comment calls them a stand-in for real geometry; they now add fine grain under the city). Two noise layers add weathering: broad tonal patches, and grime streaks stretched along the hull's length.

The height is never displaced — it only tilts the normal. That is bump mapping, introduced by James Blinn in 1978. The usual implementation needs a tangent frame per vertex; perturbNormal instead uses Morten Mikkelsen's 2010 method, which derives everything from screen-space derivatives (dFdx, dFdy: how a value changes from one pixel to its neighbour). Grooves and raised blocks then light and shade like real relief under the grazing sun.

A flat deck from above, shaded by panel(), blocks() and perturbNormal() from hull.frag (tiny WebGL figure; the weathering layers are left out). Drag to pan. Turn off region variation and the plating becomes one repeated grid; turn on region borders to see the 28-unit regions. Lower the light: seams and blocks come alive at grazing angles.

5 · Baked occlusion

In a real place, light comes from everywhere a little — the sky, nearby walls — and corners and crevices receive less of it because their surroundings block the view. That darkening is ambient occlusion; computing it properly means asking, for every point, how much of its surroundings it can see — usually by casting many rays. It became popular in film production in the early 2000s and is still expensive in real time.

The dreadnought uses the cheapest possible version, decided once at build time. Every vertex carries one extra number, Vertex::occlusion (1 = open). When the model places a block on the deck, it sets the occlusion of the block's bottom vertices lower: 0.4 for city blocks, 0.65 for stacked blocks, 0.6 for rims, 0.5 for turret bases, the tower and the engine fins, and 0.45 for the fittings that hang under the belly — on their top vertices, since those touch the hull. The GPU interpolates per-vertex values across each triangle, so every block side fades from dark at its foot to open at its top: a soft contact shadow that costs nothing per frame.

Two faces of one block, showing the interpolated occlusion value. A side face is two triangles and fades evenly from foot to top. The end faces are loft caps: a fan of four triangles around a centre vertex, and the centre is not "below the middle", so it stays at 1 — per-vertex values are only as fine as the vertices. Hover a face: the bars show how hull.frag applies that value to each light term.
// hull.frag: how the baked value is used
col  = albedo · sun · max(N·L, 0) · shadow · mix(1, occlusion, 0.5)   // direct sun: only half affected
col += sun · specular · fresnel · 1.8 · shadow · occlusion
col += albedo · (planetBounce + ambient) · occlusion                // fill light: fully occluded
col += environment · fresnel · reflect · (1 − roughness) · 4 · occlusion

6 · Shadow maps

Baked occlusion knows nothing about the sun. For real shadows — the tower's shadow across the deck, every block's shadow on its neighbours — the demo uses shadow mapping, published by Lance Williams in 1978. The idea fits in two passes:

  1. Render the scene from the light. For the sun, all rays are parallel, so this is an orthographic view (no perspective) looking along the sunlight. Keep only depth: each texel of the shadow map stores the distance from the light to the first surface it sees. In the demo this is shadow.vert plus an empty shadow.frag — no color at all.
  2. When shading a pixel, transform its position into the same light view and compare its depth with the stored one. If it is farther from the light than what the light saw, something is in front of it: it is in shadow.

Two problems come with it. The map has finite resolution: one texel covers a patch of surface, and its single stored depth must stand for the whole patch. On surfaces tilted relative to the light, half of that patch is "behind" its own stored depth, and the surface shadows itself in stripes — shadow acne. The cure is a bias, and the demo uses three: a polygon offset while rendering the map (glPolygonOffset(2, 4) pushes stored depths slightly away), a constant 0.0005 subtracted in the comparison, and a normal offset that moves the lookup point 1.5 texels off the surface along its normal.

A slice through a deck, sun from the upper left. The strip on top is the shadow map — one row of texels, brighter = farther from the light. Surfaces are gold where lit, blue where the map says "something is in front", dark grey where they face away from the sun. Set the bias to 0 and turn off the normal offset: acne stripes appear on the lit faces. Too much bias, and the shadows detach from the blocks that cast them ("peter-panning").

7 · Fitted, soft shadows

Fitting. A 2048 × 2048 map stretched over the whole ship (260 units) gives 0.127 units per texel — fine for the tower's shadow, far too coarse for a 0.3-unit block seen from a few units away in the deck drift. So the demo renders two cascades (the technique is called cascaded shadow maps): cascade 0 covers a sphere of radius 45 placed 40 units in front of the camera (0.044 units per texel), cascade 1 a sphere of radius 130 around the ship. Each pixel uses cascade 0 if it lies within 92 % of that sphere, otherwise cascade 1.

fitDirectionalShadow builds each cascade's light matrix: a view looking along the sunlight, an orthographic box of ± radius around the sphere's centre, extended 150 units toward the sun so that casters outside the sphere (the tower, for a cascade on the deck) still cast into it, and the centre snapped to whole texels. Without the snap, a moving camera would move the texel grid by fractions of a texel every frame and shadow edges would visibly crawl.

Softening. One depth comparison is a yes/no answer, so a single lookup gives hard, stair-stepped edges. Percentage-closer filtering (PCF, Reeves, Salesin and Cook, 1987) instead performs several comparisons around the point and averages the answers: the fraction lit. The GPU already does a 2 × 2 version for free when a depth texture uses linear filtering with comparison enabled; lighting.glsl takes 12 such taps spread on a Poisson disc (points spread evenly but irregularly) of radius softness × one texel (1.5 by default), and rotates the disc by a per-pixel hash so that the 12 copies of an edge blur into fine noise instead of 12 visible steps.

A 16 × 16-texel patch of shadow map (each texel is 10 × 10 pixels here), with an occluder: a dome and a diagonal edge. Step through the four modes. Hover a pixel to see where its 12 taps land; on the right, the Poisson disc and its rotation for that pixel.

8 · Cold light and reflections

Every hull pixel's color is a sum of a few light terms, each one simple. The sun is low — 18° above the horizontal, from 70° of azimuth — so it rakes across the decks and every block throws a long shadow. The sun's color is a slightly warm white (1, 0.95, 0.88) × 2.4; everything else is cold.

Fresnel's equations say how much light a surface reflects depending on the viewing angle; Christophe Schlick's 1994 approximation is F = F₀ + (1 − F₀)(1 − cos θ)⁵. With F₀ = 0.06, a plate seen face-on reflects 6 %, but seen at a grazing angle it reflects almost everything. That is the cold sheen along the hull's long flanks: they are seen edge-on, and they mirror the planet glow.

Drag the eye (cyan handle) around the surface. Left: a cross-section with the normal N, the sun L, the view V and the reflected ray R; the ring shows the environment R would see — black space above, planet glow below. Right: each term of hull.frag for this configuration (luminance) and the Fresnel curve. Tilt the surface to 180° (the belly, facing the planet): the bounce takes over.

The backdrop. Behind the hull, sky.frag traces one ray per pixel against a sphere 20,000 units in radius whose top lies 450 units below the ship: the planet, with noise continents, clouds, sun glint on the oceans, city lights on the night side and a haze toward the limb. Rays that miss it but pass near it pick up a thin atmospheric glow that fades exponentially with altitude (scale 60 units). Two layers of hashed stars and the sun's disc complete it. The demo's bloom for this entry is turned down to 0.15 with a threshold of 2.5 (timeline overrides), so only the engines, the windows and the sun really glow and the hull detail stays crisp.

9 · Choreography and scale

The scene is a list of four Shot records: where the ship starts and how fast it cruises (along +z), where the camera is at the start and end of the shot, what it aims at, and its focal length. The camera and its aim each live either in world space — hanging still while the ship passes — or in the ship's frame, riding along with it. Within a shot the camera eases from start to end with s = x²(3 − 2x), while the ship moves at constant speed: the giant is steady, only our small craft hesitates.

0 · overhead pass — ship from z = −35 at 9 u/s; camera in world space 34 units below the ship's centre line, aimed ahead and up; focal 0.9 (58° vertical field of view).The nose enters over the top of the frame and the belly slowly fills the sky, above the planet's horizon.
1 · planet eclipse — ship from z = −230 at 22 u/s; camera 160 units to the side and 40 above; focal 1.2 (45°). The whole hull crosses the frame and hides the bright limb; at this distance its true length reads.
2 · deck drift — ship from z = 0 at 9 u/s; camera in the ship's frame, from (12, 26, 10) back to (9, 27, −24), aimed at the bridge (0, 34, −83).We drift backward over the city toward the tower; the decks scroll slowly under us.
3 · departure — ship from z = 120 at 12 u/s; camera in world space beside the stern; aim in the ship's frame, on the engines (0, 0, −110).The engines pass and recede; the camera turns to follow them.

Scale cues. A camera that hangs still turns size into time: an object that takes seconds to pass is big. The ship moves past the planet, so it occludes something we know to be enormous. Its surface is covered with detail at the scale of a unit — blocks, windows, rims — two hundred times smaller than the hull, the density we associate with cities seen from the air. And the planet's horizon is visibly curved yet far below: the ship hangs in a world much larger than itself.

Top: plan view (seen from above, nose toward +z on the right) with the ship, its outline at the start and end of the shot, the camera with its horizontal field of view, the aim point and the camera's path. Bottom: side elevation with the vertical field of view. Each shot is framed to fit. Click the timeline to jump.

The algorithm, step by step

The complete scene, in order. The first half runs once, when the demo loads; the second half every frame.

// load time: big forms (makeDreadnought)
random = mt19937(20261017)
hull   = loft(hexagon, rings z = −100, −20, 60, 100,
              half sizes (55,9) (33,6.5) (11,2.8) (0.6,0.4))
for tier in 3 tiers:
    tier = loft(square, center y = ridge(z) + lift,
                half width rear → 2 at the front)
bulb   = ellipsoid under z = −45
engine = box + 3 large, 4 small nozzles + glowing discs
Lofts sweep a polygon through rings (§1). Every part is flattened(): one face normal per triangle, crisp metal. The ring tables double as functions ridge(z), width(z), tierTop(z).
// the city: 1.4-unit cells over the decks
for each cell (cx, cz):
    if near the rim or under the tower: skip
    d = fbm(cx·0.045, cz·0.045)        // district
    if rand > 0.12 + 0.75·smoothstep(.4,.62,d): skip
    size   = cell · (rand, rand) in 0.45..0.95 // rows if d > .58
    height = 0.25 + 1.6·rand^2.2·(0.6 + d)
    if rand < 0.03: tower, height 2.5..6
    corners = topSurface(4 footprint corners)
    if any NaN or max − min > 0.5: skip
    box(base = min − 0.15, occlusion(bottom) = 0.4)
    if height < 2 and rand < 0.3: stack a smaller box
Noise districts give coherent density (§3); the power 2.2 keeps most blocks low; the corner test keeps terraces clean (§2). Every block's foot gets baked occlusion (§5).
// more detail layers, same random stream
rims      along every edge: length rand(1.2..4),
          8 % missing, occlusion 0.6
turrets   every 10 units on the hull, every 14 on tiers
windows   rows on the walls where fbm(z·0.12, row·3.1) > 0.5
hangars   every 24 units on both walls
underside plates, keel ribs, hanging fittings, docking bay
tower     neck, bridge + window band, domes, masts
engine    27 radiator fins, side vents
Variation at several scales, from noise and the shared seeded generator (§3). The ship ends up near 100,000 triangles, uploaded once with position, normal, material id and occlusion per vertex.
// every frame: shot and camera
shot     = t / 9.6 s           // four equal shots
x        = time in shot / 9.6;  s = x²(3 − 2x)
shipZ    = shipStart + shipSpeed · time in shot
camera   = mix(from, to, s)   (+ shipZ if in ship frame)
aim      = mix(aimFrom, aimTo, s) (+ shipZ if ...)
viewProj = perspective(2·atan(0.5/focal)) · lookAt
The ship moves at constant speed, the camera eases; cuts land every twenty beats (§9).
// sun and shadow cascades
L = (sin az·cos el, sin el, cos az·cos el)   // 70°, 18°
cascade0 = sphere(camera + forward·40, r = 45)
cascade1 = sphere(ship + (0, 5, −10),   r = 130)
for each cascade:
    lightMatrix = ortho(±r, reach 150) · lookAlong(−L)
                  snapped to whole texels
    draw ship depth only, polygon offset (2, 4)
A tight cascade where the camera looks, a wide one over the ship (§7); the depth-only pass is the shadow map (§6).
// sky.frag: fullscreen, behind everything
color = stars + sun disc and halo
if ray hits the planet sphere:
    color = continents, clouds, glint, city lights, haze
else if it passes above the horizon:
    color += atmosphere · e^(−altitude/60) · day side
The planet is a single ray-sphere intersection per pixel; no geometry (§8).
// hull.frag, per pixel of the ship
if engine disc: return engineColor · glow
if window:      return warm/cold · hash-lit
uv     = planar(objectPos, normal)
plate  = panel(uv);  seam = smoothstep(0, .07, plate.dist)
N      = perturbNormal(n, seam·0.04 + blocks(uv))
albedo = grey · tone(plate) · seam · weathering
shadow = 12 rotated Poisson taps in the cascade
Plating, seams and bump from hashes (§4); soft shadows from the fitted cascades (§7).
col  = albedo·sun·max(N·L,0)·shadow·mix(1,occ,.5)
     + sun·specular·fresnel·1.8·shadow·occ
     + albedo·(bounce·blue·max(−N.y,0) + ambient)·occ
     + env(R)·fresnel·reflect·(1−rough)·4·occ
     + albedo·engine·glow·max(N·toEngine,0)/(1+.01d²)
// post: bloom 0.15 above 2.5, ACES, vignette, grain
The light budget (§8), with the baked occlusion applied to each term (§5). Then the demo's post chain with this entry's low-bloom overrides.

The real source

These are the files as they are in the demo's repository. The browser version above uses the same shader code (with the WebGL changes noted under the live demo) and a JavaScript port of the C++.

shaders/scenes/dreadnought/hull.frag fragment shader · the ship
shaders/common/lighting.glsl shadows · bump · Fresnel · specular
shaders/common/mesh.vert vertex shader · instanced meshes
shaders/common/shadow.vert + shadow.frag depth-only pass
shaders/scenes/dreadnought/sky.frag fullscreen · planet, stars, sun
shaders/common/sky.glsl star layers
src/scenes/dreadnought/DreadnoughtScene.cpp — shots and render() C++ · excerpt
src/scenes/dreadnought/DreadnoughtModel.cpp C++ · the ship generator
src/gfx/MeshData.cpp — append, orientOutward, flattened, makeLoft C++ · excerpt
src/gfx/ShadowMap.cpp — fitDirectionalShadow and the depth texture C++ · excerpt
src/gfx/CpuNoise.h C++ · load-time noise

Glossary

Mesh, vertex, triangle
A 3D model as a list of corner points (vertices) and the triangles joining them; GPUs draw triangles.
Loft
A surface swept through a sequence of cross-sections (rings); the name comes from the lofts where shipwrights drew hulls at full size.
Face normal
The direction perpendicular to a triangle. Giving each triangle's vertices its face normal makes flat, faceted shading.
Vertex shader
A small program the GPU runs once per vertex; it decides where the vertex lands on screen.
Fragment shader
A program run once per covered pixel (fragment); it decides the pixel's color.
Uniform
A value set by the CPU before a draw and shared by every vertex and pixel of that draw (camera, sun, time…).
Hash
A deterministic scrambling function: same input, same "random-looking" output. Used instead of stored random numbers.
Seeded random generator
An algorithm producing a long, random-looking sequence fully determined by its starting number (the seed); here the Mersenne Twister.
Value noise / fbm
Smoothly interpolated random values on a grid; fbm sums several octaves of it. Neighbouring points get similar values.
Planar mapping
Getting 2D surface coordinates by dropping the axis the surface faces most.
Bump mapping
Faking small relief by tilting the normal according to a height pattern, without moving any geometry.
Albedo
The fraction of light a surface reflects diffusely: its base color.
Ambient occlusion
The darkening of creases and corners that see less of their surroundings; baked here as one number per vertex.
Shadow map
A depth image rendered from the light; a point farther from the light than the stored depth is in shadow.
Shadow acne / bias
Self-shadowing stripes caused by the map's finite resolution; avoided by offsetting depths or lookup positions slightly.
Cascades
Several shadow maps of different coverage: sharp near the camera, wide over the whole scene.
PCF
Percentage-closer filtering: averaging several shadow comparisons to get a soft fraction lit.
Poisson disc
A set of sample points spread evenly but irregularly, so no two are too close; avoids grid patterns.
Orthographic projection
A projection without perspective: parallel rays, sizes independent of distance. Used for the sun.
Lambert's law
Diffuse light is proportional to the cosine of the angle between the surface normal and the light: N·L.
Specular, Blinn-Phong
The mirror-like highlight of a light source; Blinn-Phong models it with the half vector between light and view.
Fresnel effect
Surfaces reflect more at grazing angles; Schlick's formula approximates it.
HDR
High dynamic range: keeping brightness values above 1.0 until a final tone-mapping step.
Tone mapping (ACES)
A curve compressing HDR brightness into the screen's range while keeping highlights pleasant.
Bloom
Letting very bright areas glow into their neighbourhood, like light scattering in a lens or eye.

Part of the Space Travel documentation. Direction & design: blacklizard · Code: Claude. The figures on this page are computed live in your browser; nothing is pre-rendered except the header image and the three captures.

← 09 · Fleet  ·  All scenes  ·  11 · Supernova →