See before you implement
Read Light & space, then compare Combined and Direct in the lab. Visit the Methods Library for visual explanations. Finish with the glossary whenever a term feels unfamiliar.
A local browser framework for detailed architectural scenes, inspectable indirect lighting, and consistent navigation. The lab is one application of it.
Local ES modules · Three.js r186 · Backend lifecycle v1 · Not yet a published package
You do not need to know how a GPU works to explore the lab. Start with a room, a sunlit patch and a shaded surface. Change one thing and watch what happens. This manual connects those observations to the code that produces them.
Read Light & space, then compare Combined and Direct in the lab. Visit the Methods Library for visual explanations. Finish with the glossary whenever a term feels unfamiliar.
Follow the first-room tutorial, inspect the scene contract, then alter the roof opening or wall colour. Familiarity with JavaScript modules is enough to begin.
Read the transport stages, frame composition, backend ownership and verification. The factorization plan distinguishes existing adapters from algorithms still to be extracted.
A scene is an arrangement of surfaces. A renderer decides the colour of each visible pixel using those surfaces, the camera, materials and lights. FMVisual coordinates those pieces and supplies original procedural architecture. Three.js supplies the current GPU lighting algorithms.
The architecture is inspired by the qualities of great atrium and Sponza demonstrations: layered arches, deep shade, textured surfaces and sunlight entering sheltered space. These are original generated scenes. Running them requires no downloaded Sponza model, cloud machine or account.
cd ~/FMVisual
npm ci
make app PORT=4173
# Open http://127.0.0.1:4173/framework.htmlUse the dependencies in the lockfile and the Node version required by the installed Vite package. Tracker Desk launches the same local application. The manual itself does not start a renderer; the lab and runnable tutorial do.
Triangles describe stone walls, arches, textiles and columns. A camera projects those surfaces onto the screen. Texture maps give them colour and small surface variations. An embroidered edge can remain visually crisp even when the lighting grid is much coarser.
Voxel GI places the world inside a three-dimensional grid. Each small cell is a voxel. Occupancy records where surfaces exist; radiance stores a filtered approximation of light leaving them. The grid helps estimate light arriving from surrounding surfaces without tracing every possible path.
Screen resolution controls the visible image. Voxel spacing controls how finely the lighting representation distinguishes surfaces. More screen pixels will not make a thin wall survive a coarse voxel grid. More geometric carving will not automatically improve light transport. Design both scales deliberately.
Tracing a small number of cones is affordable but produces an imperfect estimate. Temporal filtering combines evidence across frames, using depth and motion to find the previous location of a surface. Stable views gain smoothness. Newly revealed areas must reject old information or they can smear. History resets are essential after camera cuts, output changes and changes to lighting.
The combined view uses temporal resolve when enabled. Diagnostic views deliberately use stationary cones and bypass the final temporal resolve. Treat those views as instruments for a specific representation rather than a photograph of the finished room.
The application supplies scenes, settings and a container. The framework owns the camera, controls, animation loop, resize handling and rendering lifecycle. Each scene owns its geometry, materials and textures.
Buttons, sliders, light studies, settings export and diagnostic views.
Scene registry → camera & navigation → lighting & budgets → backend lifecycle.
Indirect light, occlusion, temporal resolve, shadowed atmosphere and bloom. Optional planar reflection belongs to the runtime.
The same scenes and navigation with direct lighting. GI and unsupported effects are labelled and disabled.
Select a stage to see its inputs, outputs and current implementation. The independent algorithm replacements remain planned work.
Actual frame composition: depth / normals / motion → voxel irradiance & visibility → material lighting → optional shadowed air → temporal resolve → restrained bloom. Reflection uses an additional clipped scene camera.
The runtime initializes a renderer, tries WebGPU and falls back to WebGL when necessary. It creates the camera, controls, directional sun and ambient light. A scene factory returns fresh resources. Bounds are supplied or inferred, the camera is placed, budgets are resolved and the chosen backend creates its passes. A resize observer fits the canvas to the container.
| Change | Runtime / backend response | Author’s responsibility |
|---|---|---|
| Scene ID | Create the next world; release old backend and reflection resources; detach and dispose the old world; build the new backend. | Factory must return fresh resources with valid bounds and camera data. |
| Quality preset | Rebuild voxel and rendering passes at the new resolution and cone count. | Expect a transient allocation / compilation cost. Validate coverage budgets first. |
| Output or lighting | Update the selected route / light configuration and invalidate temporal history where required. | Pass a complete settings object. Keep diagnostic comparisons otherwise identical. |
| Container resize | Resize the renderer and update camera aspect. | Give the container nonzero width and height. |
| Geometry animation | No general host-level dynamic-geometry invalidation contract is qualified yet. | Do not claim arbitrary moving meshes have correct cached GI. Leave static shadow caching off and qualify the entire update path. |
| Capability | Current behavior | Implementation / limits |
|---|---|---|
| Indirect diffuse light | Voxel cone tracing, directional radiance hierarchy, cached bounces and occlusion. | Three.js VXGI. Independent voxelization and transport are not yet implemented. |
| Temporal stabilization | Combined lighting resolves depth and velocity across frames; raw mode is selectable. | Three.js TRAA. Dedicated ghosting and disocclusion qualification remains open. |
| Shadowed atmosphere | Optional half-resolution raymarching against the directional sun shadow map, with filtering and depth-aware composition. | Three.js Godrays, bilateral blur and depthAwareBlend. Opaque-depth approximation. |
| Planar pool reflection | A clipped half-resolution mirrored camera with angular weighting. | Three.js ReflectorNode. No fluid simulation, refraction or traced specular GI. |
| Architectural surfaces | Procedural colour, relief and roughness for stone, embroidery and linen. | FMVisual. Local deterministic maps, mipmaps and shared-resource disposal. |
| Lighting studies | Morning veil, The last light and Silver hour apply complete lighting settings. | Authored presets. Changing individual lighting sliders creates a custom study. |
| Diagnostic outputs | Combined, direct, bounce irradiance, occlusion, radiance and voxel opacity. | Debug views bypass atmospheric composition and use stationary cones. |
| Navigation | Shared WASD, mouse look, Orbit, camera presets and slow camera rails. | Sanctuary: authored planar containment and obstacle sliding. Reference scenes retain free movement; gravity and stairs are not simulated. |
Acceleration and deceleration are smoothed. Walking starts at 3.2 units per second; Shift raises it to 7.2. Hold W while turning to follow a curve. Looking up does not turn forward movement into flight.
UI fields, sliders, buttons and system shortcuts retain their keyboard behavior. Taking control stops a camera tour. Leaving the window clears held keys.
When mouse capture is unavailable in an embedded view, drag to look and use WASD while the scene is focused. Orbit provides drag-to-orbit and scroll-to-zoom.
Factories return fresh scene objects with a group, camera position, target and a disposal function. Optional metadata adds camera studies, a slow rail, scene lighting, a planar pool and a reference span for quality-preserving growth.
The first extension keeps the original atrium geometry as an exact fixture. The lantern passage and sun court belong to one continuous world, not a loading transition.
Resolution grows with world span to preserve reference voxel spacing. The expanded world uses 118 rather than 80 on its longest axis at Balanced quality, keeping approximately 0.355 world units per voxel.
The runtime rejects growth beyond a 256-voxel longest axis or a 4096 shadow-map dimension. A further substantial radius requires lighting zones.
Shadow coverage fits the world while retaining at least the reference texel density. Static scenes may cache sun shadows between changes.
The live FPS readout and radius benchmark measure frame intervals. They are not GPU timestamps or a portable performance guarantee. GPU timing and memory qualification remain tracked work.
A floor, three walls, two roof pieces and two neutral blocks make a useful lighting experiment. The left wall is red. The central roof slot admits direct sun; surrounding surfaces let you inspect diffuse transfer. This example imports only the public framework and Three.js.
Open the working light room ↗first, supplies a quality map and complete settings, then awaits the runtime.src/examples/first-scene.js, change the red wall to blue or widen the roof slot. Save and reload. Observe which surfaces change in Direct, then Combined.These listings are imported from the executable source at build time. They are not separately maintained pseudocode. The example is also included in the production build at /examples/first-scene.html.
settings() validator intentionally accepts only its built-in scenes and presets. A new host should provide its own validated settings, as this example does. Passing first through the lab validator will fail.Import from src/framework/index.js. Register scene factories, pass settings and quality presets, then keep the returned runtime until the application closes.
const scenes = createSceneRegistry({ gallery: createGallery });
const app = await createVisualization({
container, scenes, settings, qualities, onFrame
});
app.update(nextSettings);
app.resetCamera();
app.dispose();group, finite camera and target triples, and dispose() are required. Scene bounds support coverage calculations. Geometry, materials and shared textures belong to the scene.
Optional fields: cameras, tour, lighting, reflectivePool, and voxelReferenceSpan.
Adapters implement render(), update(), resetHistory() and dispose(), and declare capabilities. Hosts can inject createBackend(context).
The context contains renderer, scene, camera, settings, quality presets, the sun and a volume-quality resolver. Backends own their passes and history resources.
| Field | Shape / default | Meaning |
|---|---|---|
group · required | Three.js Object3D, typically Group | All owned visible meshes. Create fresh objects on each factory call. |
camera, target · required | Finite three-number arrays | Default eye and look-at positions. Keep them distinct and outside opaque walls. |
dispose() · required | Function | Release owned geometry, materials and textures exactly once per resource. Removing an Object3D does not release its GPU allocations. |
bounds | Box3; inferred from group if omitted | Nonempty world-space coverage. Inference happens during registry creation of a world, not continuously as meshes move. |
backdrop, navigation, viewDistance | Optional context Object3D, authored walk areas / obstacles, far-plane distance | Separate layer-2 scenery from nearby transport. Dispose context with the world. Walk metadata opts into planar containment; it does not add gravity or stairs. |
title, description | Strings | Readable host presentation, exposed by the runtime. |
cameras | Map of IDs to {position, target} triples | Authored views selected with settings.camera. Missing ID falls back to the default eye and target. |
tour | {positions, targets}; matching arrays of ≥ 4 triples | A closed, arc-length sampled camera rail. Target paths control gaze. Build a clear route; there is no collision correction. |
lighting | {sun, ambient, sky, background, staticShadows} | Sun intensity defaults to 5, ambient to .32. Background defaults to #b6c1c1. Sky is opt-in. Set staticShadows only for static geometry. |
reflectivePool | {width, length, position} | Declare one horizontal reflection plane; WebGPU path only. It is not general curved-surface reflection. |
voxelReferenceSpan | Positive reference longest-axis span | Preserve voxel spacing as bounds grow; also opt into fitted world-covering sun shadows. Omit for small standalone scenes. |
| Member | Use |
|---|---|
update(nextSettings) | Replaces the settings object; it is not a patch. Merge into your host’s current settings first. Updating a disposed runtime throws. |
resetCamera() | Return to the selected authored camera and invalidate history. This does not reconstruct the scene. |
dispose() | Idempotent teardown of loop, observer, controls, backend, scene resources, effects and renderer. The canvas is removed. |
gpu | Whether the actual renderer is using WebGPU. A custom backend’s capabilities must still be considered separately. |
cameraState | Current position, target, availablePresets, motion, tourAvailable, navigation, pointerLocked and dragLookFallback. |
qualityState | Resolved volume metadata and shadow map coverage. Includes host and actual upstream volume coverage, plus renderer allocation counters. These are not complete driver VRAM measurements. |
features | Atmosphere / reflection flags and reflection availability for the built-in path. |
title, description | The current world’s readable metadata. |
onFrame(event) receives one of: {fps, frameMs, gpu}, {error}, {motion: 'still'} or {navigation: {enabled, locked, dragFallback}}. Inspect the fields present before updating UI. Navigation events may arrive during initialization, before the awaited runtime variable is assigned. Use the event data directly there.
createBackend(context) is synchronous. Context contains renderer, scene, camera, settings, qualities, sun, volumeQuality. Call volumeQuality(id) for the actual density-preserving resolution. The adapter must provide render(), update(next), resetHistory(), dispose() and a boolean capabilities.indirect. Built-in adapters also report capabilities.temporal and a readable name.
Backend version 1 expresses this local contract. It is not a stable external plugin ABI. A replacement must define who owns every pass and texture, respond correctly to updates, and prove its capabilities with actual images. Naming an adapter VXGI does not supply an implementation.
These ranges are the lab validator’s current supported range. The reusable runtime does not apply that validator for you. Custom hosts must validate their own scene IDs, quality keys, camera IDs and numeric values. Start with the complete tutorial object.
| Settings | Supported lab values | Practical meaning |
|---|---|---|
scene, quality | Registered scene; draft / balanced / fine | Custom hosts can use their own registry and quality-map keys. |
view | combined / direct / gi / ao / radiance / opacity | Final image, direct shading or a transport diagnostic. GI-only views require WebGPU with built-in adapters. |
sun, elevation | 0–360° azimuth; 8–85° elevation | Move the directional sun around / above the world. Low angles make longer shadows. |
exposure | .3–2.5 | Display brightness multiplier before tone mapping; affects the whole image. |
indirect | 0–3 | Scale indirect diffuse illumination. Zero is a useful controlled comparison. |
aperture | 15–80° | Cone width; trades angular specificity against a broader estimate. Larger is not universally better. |
bounces | 0 / 1 / 2 | Upstream cached bounce setting, not a promise of path-tracing convergence. |
temporal, bloom | Boolean | Temporal stabilization and bright-region glow for the combined view. |
atmosphere, haze | Boolean; 0–1 | Enable and tune shadowed air in the combined WebGPU view. Air is an effect, not diffuse bounce lighting. |
reflections | Boolean | Enable a scene-declared planar pool on WebGPU. No pool declaration means no reflection. |
study | morning / ember / silver / custom | Select sun colour. The lab’s preset buttons also apply a complete group of numeric light settings; assigning the ID alone does not apply those numbers. |
camera, motion | Authored camera ID; still / tour | Preset eye / target and optional rail movement. Tour requires world.tour. |
navigation | walk / orbit | Lab defaults to Walk. A framework host omitting this field retains Orbit. |
Let S be the longest world span, S₀ the optional reference span and R₀ the preset resolution. The actual longest-axis resolution is R = ceil(R₀ × max(1, S / S₀)) when a reference span is provided; otherwise R = R₀. Voxel spacing is S / R. The actual allocated dimensions include padding and mip alignment.
At Balanced quality the original atrium’s span is about 28.485 units and the extension’s is 41.835. Resolution rises from 80 to 118, keeping spacing near .355 units. The aspect-aware padded grid grows from 197,120 to 286,720 base cells. These are base-grid cell counts, not total texture bytes; directional levels, intermediate passes and temporal buffers add cost.
Extending one axis is cheaper than scaling all three. Doubling every axis while keeping spacing fixed needs roughly eight times as many base cells before padding. Raising screen resolution additionally increases screen-space pass cost. More cones and bounces add work independently. Measure one variable at a time; the largest quality preset is not a universal default.
Freeze the camera. Turn atmosphere and bloom off. Use a fixed light study. Compare Direct, Combined and the relevant diagnostic output with identical settings. Preserve the failing case before changing several controls.
| Symptom | First check | Useful next action |
|---|---|---|
| Only direct lighting / GI controls disabled | Actual backend label, browser WebGPU availability and console errors. | Confirm a WebGPU-capable browser/device. Fallback is deliberate; a checkbox cannot add voxel GI to it. |
| Black or invisible canvas | Initialization error, container dimensions, camera inside a wall, valid nonempty bounds. | Try the first-room example. Inspect startup errors before adjusting exposure. |
| Light leaks through thin walls | Opacity representation, wall thickness relative to voxel spacing and coverage. | Increase wall thickness or compare finer quality at a fixed camera. Preserve a minimal leak fixture; bias changes alone can hide a defect. |
| Shimmer on moving edges | Raw vs filtered Combined, depth/velocity consistency and fine detail below pixel size. | Stop motion and compare. Record thin edges and disocclusions for temporal qualification. |
| Smears after a cut or lighting change | History invalidation and whether the host used the runtime update/reset API. | Reset camera/history. Reproduce with a stable fixture; arbitrary renderer mutation can bypass lifecycle handling. |
| Flat, washed-out architecture | Exposure, indirect intensity, atmosphere and bloom separately. | Restore the study, disable effects, then add them one at a time. Preserve dark recesses alongside bright openings. |
| Choppy motion or growth-budget error | Frame intervals, active resolution / cones, world bounds and extra effect passes. | Compare Draft, disable reflection / atmosphere and inspect spacing. Do not shrink coverage silently to make a number look better. |
| WASD does nothing | Walk selected, scene focused, editable field not active. | Click the canvas. Use drag-look if pointer capture is denied. Escape releases control. The sanctuary has planar containment; stairs and gravity are not simulated. Reference scenes remain free. |
| Unknown scene / unsupported setting | Registry ID vs the lab’s restricted validator. | Register a factory in your host and validate against that registry. Keep complete settings on every update. |
| Memory climbs after switching scenes | Shared-resource ownership and scene dispose implementation. | Release textures separately from materials, release shared assets once, and inspect repeated create / switch / dispose runs. Full memory qualification remains open. |
Begin with the first-room factory. Keep construction deterministic, return fresh resources and define an intelligible default camera. Check nonempty bounds and disposal. A lab addition also needs its scene ID, UI option, description and settings validation updated.
Add a cameras entry with eye and target triples. For a tour, author matching closed position and gaze paths with at least four points. Inspect the entire route through doorways and around columns; a pretty starting frame does not prove clearance.
Inject a lifecycle-compatible adapter. Start with one controlled fixture and the same camera / lights. Compare output, reset behavior and resource cleanup before claiming a reusable transport replacement.
| Module | Responsibility |
|---|
The teaching method prepares ten real students to continue FMVisual with decreasing teacher intervention. Everyone first builds and explains a small room individually. Five pairs then rotate through architecture, materials, lighting, navigation and integration, before an independent transfer and a recipient handoff.
Assess explanation, a prediction written before execution, implementation, diagnosis, cost reasoning and handoff. Each dimension must reach at least 2 on a 0–3 rubric; a total score cannot conceal a missing skill.
Record teacher help when it happens: no help, an attention question, a targeted hint or a supplied solution. Work completed with a supplied solution is assisted completion.
Another learner must be able to start, explain and modify the contribution using its instructions. The author observes where the handoff breaks.
Predict what a wider roof opening or a blue wall will do in Direct and Combined. Keep the camera and lighting fixed. Then implement a second camera, diagnose an isolated partial-settings update, verify a density-preserving expansion and hand the room to a recipient. Keep each learner's copy separate from the production lab.
The technical rehearsal checked the exercise on WebGPU and direct fallback, detected deliberate update and density mistakes, and led to a clearer colour-lighting condition. The human pilot is prepared; students have not yet participated. A learner pilot additionally needs real submissions, recorded help, transfer attempts and handoff observations. An AI-agent pilot tests agent continuation under stated conditions; it does not establish human learning or retention.
make check runs settings, geometry, lifecycle contracts, camera-rail clearance, volume density, shadow coverage and navigation tests, then builds the lab, manual, library and first-room example.
npm run test:browser exercises actual WebGPU and forced direct fallback, real controls, camera studies, effect comparisons, full screen and mobile layout.
Local captures and reports are generated under artifacts/. The radius comparison runs reference and extension in ABBA order with warmup and frame-interval sampling.
Tracker data lives centrally in ~/tracker-factory/trackers/fmvisual. Autopilot remains off; completion uses each task’s verification command.
A finite scene can imply a much larger world. Design reachable space, middle-distance architecture, the horizon and transitions separately. A distant roofline may need only a convincing silhouette; the floor beneath your feet needs geometry, navigation and valid lighting coverage.
Start with a deliberate walkable envelope and composed views through every exit. Add low-cost context before another fully detailed wing. For larger growth, prepare the next region early and use overlapping lighting zones rather than stretching one grid until detail disappears.
Keep distant context from inflating nearby lighting bounds without an explicit coverage policy. Test backtracking, fast movement, camera rails and reflected views. The Hillside sanctuary now has contained walking, a valley arcade, cypress garden and a separate distant landscape. Its reference scenes remain unchanged. The world is fully resident; multiple lighting zones remain FMV-25.
Explore boundaries & horizons ↗The design brief is World boundaries and staged expansion.
Imagine leaving the arcade, descending through the garden, passing beneath the aqueduct and entering a house that is now a distant silhouette. On the return, the sanctuary should keep its shape, its stonework and the same sheltered light. That journey is our next architectural target.
Stable region addresses, terrain recipes, roads, landmarks and saved changes describe places even when their detailed objects are absent from GPU memory. A house must keep its doorway and identity when it is recreated.
Prepare the next region before you reach it. Keep nearby geometry, navigation and lighting detailed; represent distant places with simpler terrain and silhouettes. Release unneeded resources behind you, with room for backtracking and reflected views.
Balanced lighting currently uses 499,200 base cells at about .356-unit spacing. The distant landscape is a separate visual layer. Making that landscape visitable requires terrain navigation, stable detail transitions, overlapping lighting zones and bounded loading. Those systems are planned; the current world is fully resident.
Each extension must pass an out-and-back journey, moving-camera quality checks, repeated unloading and the same-camera cost comparison. A direct preview checks the route; real WebGPU is needed to qualify voxel lighting. The detailed plan separates present evidence from future requirements.
Read the whole-world plan & budget procedure ↗Geometry extraction, conservative occupancy, injection, directional hierarchy and cone integration behind the existing backend seam.
Disocclusion and thin-edge stability, GPU timestamps, memory accounting, device loss and repeated create/switch/dispose behavior.
Overlapping lighting volumes with stable doorway transitions. General collision physics, floor following and stairs. The sanctuary already has authored planar containment.