Lantern
One room, chosen for its lighting rather than for its geometry: daylight coming in through a window, a mirror-grade panel and a rough metal block that have no light of their own, a coloured wall the sun bounces off, and a warm lamp orbiting the middle of it. Not a game — the lighting is the content.
what you are looking at
Five things, and each one is an effect being asked a question.
The sun through the window. The opening is above eye height, so the light comes in over the camera and lands on the floor rather than in the lens. The room is built out of slabs rather than single quads, and that is what makes the shaft exist at all: back faces are culled in the shadow pass too, so a wall one quad thick writes nothing into the shadow map and casts nothing onto the floor.
The shadowed floor. Two lights reach it. The sun draws the window's own shape onto it, and the orbiting lamp is the room's only point light — so it is the candidate the shadow atlas hands its light tiles to, and the contact shadows that swing round the floor are its. Its reach is under half the room's depth on purpose: a light that touched every wall would light the room evenly and there would be nothing to watch it move against.
The coloured wall's bounce. The red wall throws colour back into the room, and it is one analytic gather of the sun's first bounce off this room's interior, baked into the irradiance volume the scene carries. One bounce off one box — nothing inside the room occludes it, and nothing bounces twice. It is not a global-illumination solve and the sample does not claim to be one.
The two metals, lit by reflection alone. A fully metallic surface has no diffuse lobe for an ambient term to multiply, so nothing but a reflection lights either of them. The screen-space march reaches the foot of the mirror panel and only the foot — the panel faces you, so a ray leaving it travels back past the eye, and only the lowest band sends one that still hits the floor on screen. Everywhere above that band the march finds nothing and falls back to the same baked volume, which is why the face is dim rather than black. The rough block never fakes a sharp hit: its roughness is above the cutoff one ray is honest at, so it takes that environment directly, with the sun's own specular on top.
Press F3 and the panel's unbuilt section says the
same three things on screen, because a fixture whose job is showing what the
renderer does must not flatter it.
controls
Nothing to play. There is a camera, and there are switches.
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
-
Turn shadows, ambient occlusion or reflections off and on
their rows on the same menu —
SHADOWS,AO,REFLECTIONS - 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 a browser can only draw it one way, and that is the point.
The engine picks its lighting path from what the device offers. WebGPU
exposes no ray query, so this page draws the room through
LightingPath::Rasterised by construction — shadow maps,
screen-space ambient occlusion, screen-space reflections and a probe volume,
with no branch anywhere for it to take instead. The same sample on a Vulkan
machine with ray tracing is where the other path lives, and comparing the
two is what the fixture is for. This is the half you can look at without
building anything.
Press F3 for the receipts. The panel names which geometry path, which binding model and which lighting path this frame was drawn through, and which effects survived the four layers that resolve them — so a picture you think looks wrong comes with the arm that drew it, rather than a guess.
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 room's
geometry is built from literals in apps/lantern 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.