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.
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.
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.
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.
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?"
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:
- Is there a block? A smooth noise value, the district (§3), sets the probability:
0.12 + 0.75 · smoothstep(0.4, 0.62, district)— from 12 % in quiet districts to 87 % in dense ones. Cells too close to the hull's edge or under the command tower are skipped. - Footprint 45–95 % of the cell in each direction; in dense districts (district > 0.58) one block in four becomes a long "industrial row", 2 to 3.5 times longer.
- Height
0.25 + 1.6 · r2.2 · (0.6 + district): raising a random numberrto the power 2.2 makes most blocks low and a few tall, and dense districts grow taller. Three percent become thin towers 2.5–6 units high. - The footprint test: the deck height is read at the block's four corners. If any corner is off the ship, or the corners differ by more than 0.5, the block is rejected. That single rule keeps blocks from straddling a terrace step or overhanging the edge, so the tiers stay crisp.
- Placement: the box's base is the lowest corner minus 0.15 — sunk slightly, so no gap opens under it on the sloping ridge. Three in ten blocks lower than 2 units get a smaller block stacked on top.
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.
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.
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.
// 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:
- 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.vertplus an emptyshadow.frag— no color at all. - 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.
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.
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.
- Sun diffuse: Lambert's cosine law,
albedo · sun · max(N·L, 0)— a surface facing the sun receives the most light, a surface at grazing angle almost none — times the shadow factor and half the occlusion. - Sun specular: a normalized Blinn-Phong highlight (Blinn 1977), exponent
2 / roughness², scaled by(power + 8) / 25(≈ the textbook (n + 8) / 8π, which keeps a sharp highlight from carrying more energy than a broad one). - Planet bounce: the planet below is huge and bright, so surfaces facing down receive its blue light:
(0.18, 0.3, 0.55) · max(−N.y, 0) · bounce. This is what makes the belly readable in the overhead pass. - Ambient: a tiny cold constant, 0.01, so nothing is perfectly black.
- Environment reflection: there is no environment map. The "environment" is a formula of the reflected direction
R: almost black space above, the planet's blue glow below —smoothstep(0, −0.4, R.y)— weighted by Fresnel, roughness and the reflect slider. - Engines: the seven discs and the windows are emissive (they just output a bright HDR color); the engines also wash the
stern with blue light that falls off as
1 / (1 + 0.01·d²)and pulses on every beat.
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.
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.
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.
