The scene environment — the ONE lighting input Luxar's otherwise light-free scene
has, built lazily for physical mesh materials.
Luxar's four geometry types are emissive by design and the house mesh shader lights
itself from a fixed view-space key, so the scene has never held a light or an
environment map. A physically based material renders BLACK without one. Rather than
add light objects to the graph (spec MESH_PHYSICAL_MATERIALS_SPEC.md §3.3 / §5),
the viewer sets scene.environment from one of three sources, in this precedence:
A baked map attached to the store (luxar env bake / env attach) whose
scene_content_hash matches the scene — the raw six-face capture, prefiltered by
three at load in milliseconds. Zero live cost for a published scene.
The authored source (viewer_config.environment.source): scene — an EXACT
cube capture of the scene itself from the probe (./cube-capture.ts), so metals
and glass reflect the data they sit in; re-captured on data commit, slice change
and appearance change once the loader settles, never per frame (a fixed-probe
cube map is view-independent, so camera motion is NOT a trigger); or hdri — an
equirectangular image (./hdri.ts), with the room standing in until it arrives.
The room — three's procedural RoomEnvironment prefiltered once: no asset, a
neutral key, believable reflections, and what three's own examples light with.
Two properties are load-bearing:
Lazy. Nothing is built until SceneEnvironment.ensure is called, and
the only caller is the material manager's physical-material hook. A scene with no
physical mesh keeps scene.environment === null, so it renders byte-identically
to before this module existed — whatever its config says.
Invisible to house materials.scene.environment is read only by three's
lighting-model materials. Every Luxar material is a ShaderMaterial /
NodeMaterial with its own fragment code that never samples an environment, so
setting it changes nothing about points, lines, splats or house meshes — a
property the unit tests assert rather than assume.
The backend-specific pieces (PMREM generator, cube render target) are injected as
factories; the WebGPU ones are reached through rendering/tsl/registry.ts to keep the
lazy chunk lazy (see createSceneEnvironment).
The scene environment — the ONE lighting input Luxar's otherwise light-free scene has, built lazily for physical mesh materials.
Luxar's four geometry types are emissive by design and the house mesh shader lights itself from a fixed view-space key, so the scene has never held a light or an environment map. A physically based material renders BLACK without one. Rather than add light objects to the graph (spec
MESH_PHYSICAL_MATERIALS_SPEC.md§3.3 / §5), the viewer setsscene.environmentfrom one of three sources, in this precedence:luxar env bake/env attach) whosescene_content_hashmatches the scene — the raw six-face capture, prefiltered by three at load in milliseconds. Zero live cost for a published scene.viewer_config.environment.source):scene— an EXACT cube capture of the scene itself from the probe (./cube-capture.ts), so metals and glass reflect the data they sit in; re-captured on data commit, slice change and appearance change once the loader settles, never per frame (a fixed-probe cube map is view-independent, so camera motion is NOT a trigger); orhdri— an equirectangular image (./hdri.ts), with the room standing in until it arrives.RoomEnvironmentprefiltered once: no asset, a neutral key, believable reflections, and what three's own examples light with.Two properties are load-bearing:
scene.environment === null, so it renders byte-identically to before this module existed — whatever its config says.scene.environmentis read only by three's lighting-model materials. Every Luxar material is aShaderMaterial/NodeMaterialwith its own fragment code that never samples an environment, so setting it changes nothing about points, lines, splats or house meshes — a property the unit tests assert rather than assume.The backend-specific pieces (PMREM generator, cube render target) are injected as factories; the WebGPU ones are reached through
rendering/tsl/registry.tsto keep the lazy chunk lazy (see createSceneEnvironment).