Alcove
One walled court of nothing but occlusion geometry: a recess, a cantilevered stair, boxes and a post resting on a floor, a slot the sun runs straight down, and a sphere on a pedestal against the far wall. Flat and untextured on purpose — texture detail is exactly what hides an occlusion artefact. Not a game: the occlusion is the content.
the occlusion, from the page
Every knob the sample binds to a key, as something a finger can reach.
Natively these are keys and rows on the pause panel. A seam that can only be
nudged by , and . is a control a phone cannot reach at
all, so each one below drives the same r_ssao_* console
variable the key writes — one cell, three writers, no copy of the state on
this page. Each control shows what the console holds after it
wrote, so a value the engine clamped reads back where it actually landed.
The two view buttons are a second kind again: they name a debug view, which
is the engine's own cell — one shared with every other sample and with the
debug_view console command — and the engine holds exactly one
of them, so pressing either replaces whatever the other was drawing.
- Draw the occlusion channel instead of the shaded court
- Draw the bent direction instead of the shaded court
- Which gather runs
- Gather a bent direction beside the scalar
- The comparison seam
- …
- …
- …
- Every knob back to what the engine ships
n × 0.5 + 0.5, so +X is red, +Y green and +Z blue. Open floor
is flat green, because nothing is leaning it off vertical; a crease or a
corner leans it out towards the opening, and the colour shifts with it. Turn
the button above it off and the whole frame goes mid grey — that is the
sentinel for "no direction was gathered", and it is what the ambient term
then falls back to the shading normal for.
what you are looking at
Four things, and each one is an occlusion claim you can check.
The alcove's back corner. Two walls and a ceiling meeting is the deepest occlusion in the court, and it is where widening the radius shows first: take the slider up and the darkening reaches further out of the corner rather than getting darker in it.
The contact bands. Boxes and a post rest on the floor, and the thin dark line where they meet it is the whole of what a screen-space gather can say about contact. Open floor away from them must not move at all — if the picture darkens everywhere, what you are looking at is the intensity rather than the occlusion.
The slot the sun runs down. The sun's azimuth, the camera's eye ray and the slot's axis are deliberately one line, so the floor at the bottom of it is in full sun at any depth. Occlusion takes the same share off that crease with the sun on as with it off, because it scales the ambient term and leaves direct light alone — which is what the golden suite measures as a difference of differences rather than as a ratio.
The sphere against the far wall. A silhouette must not print onto the wall two metres behind it. That halo is the classic screen-space artefact — a depth buffer knows how far away the sphere is and nothing about what is behind it — and this court exists to make it visible if it comes back.
controls
Nothing to play. There is a camera, and there are the knobs above.
The page opens on the fixed pose the golden frames are taken from, held still. Swapping to the free camera is a row on the pause menu rather than a key, because the two cameras share one lens — a free camera with a lens of its own would give you a frame nobody could hold against the reference.
-
Swap between the fixed pose and the free camera
ESC, then ENTER on
CAMERA - Fly the free camera W A S D, Space and Shift for up and down, the arrows to look
- The AO-only view, the bent-direction view V, N
- The technique, and whether a bent direction is gathered T, B
- The seam: raise it, then walk it X, then , and .
- Radius, intensity, and everything back [ ], - =, R
- Pause and resume Esc
- Fullscreen F11
- Debug overlay F3
- Debug console `
- Pause, fullscreen and the debug overlay keys — unless a control for them is listed above
- Debug console, and its on-screen keyboard the CONSOLE button, once you have touched the canvas
why this one is worth publishing
Because the comparison is the sample, and a browser can carry it.
The engine picks its lighting path from what the device offers. WebGPU
exposes no ray query, so this page draws the court through
LightingPath::Rasterised by construction — which is the honest
answer for a fixture whose ray-traced rung is not built yet, and it leaves
screen-space occlusion carrying the whole picture. That is the tier being
compared here: two gathers, either side of one seam, in one frame, with the
debug panel pricing each.
Press F3 for the receipts. The panel names which geometry path, which binding model and which lighting path this frame was drawn through, which effects survived the four layers that resolve them, and what the occlusion chain's passes cost — so a picture you think looks wrong comes with the arm that drew it.
what is actually running
The native sample, with three differences the browser forces.
Start-up is polled across several frames, because
requestDevice is a promise and the main thread cannot block on
it. The frame loop is driven by requestAnimationFrame out here
rather than by a loop inside the engine. And the clock is the
browser's, because wasm32-unknown-unknown has no
Instant.
There is no fourth difference about saved data: this demo keeps nothing,
because there is no score to keep. It loads nothing either — the court's
geometry is built from literals in apps/alcove rather than read
off a disk, and the shaders and the font atlas are compiled into the module.
console.log, so log lines are queued
inside wasm and drained by the page each frame. crcbl.audio(),
crcbl.saves() and crcbl.logLevel(4) are there too.