Sky-derived material lighting

Daylight now supplies sky-enabled scenes with an environment texture generated from the same procedural sky uniforms as the visible background. Sunset colors, cloud cover, storm dimming and moon phase affect reflective and rough PBR materials. A dark lower hemisphere approximates ground contribution. This is directional image-based lighting, not scene-wide global illumination or an exact atmospheric scattering model.

Consumer API

const light = new Daylight(scene, renderer, {
  sky: true,
  skyIBL: true,       // default; sky:false always keeps the neutral studio map
  iblResolution: 64, // cube face width; rounded to a power of two within 32–128
  iblInterval: 2,    // minimum wall-clock seconds between ordinary refreshes
});
light.setHour(17.6);
light.update(time, focus, camera);

Existing constructors, light fields, configure, setHour, update, activeDirection and dispose remain available. iblInterval has a minimum of 0.5 seconds. IBL resolution is fixed for the instance; configure(quality) continues to configure shadows. Use skyIBL:false to retain the neutral map while displaying the sky. A studio using sky:false retains the original neutral gradient, ambient color and time-dependent environment intensity.

Rapid settings changes are coalesced. A pending final change completes through one timer even if the simulation is paused. After capture, scene emits environmentchange; Engine.mount owns a listener that invalidates its display. A consumer using a plain Three.js scene should attach its own render-request listener. Asynchronous capture failures emit environmenterror with an error field and are logged. Disposal cancels pending work.

light.refreshEnvironment() respects the throttle. light.refreshEnvironment({force:true}) explicitly bypasses it for a one-off inspection or offline capture; do not call the forced variant from a frame loop. light.getEnvironmentStats() reports mode, cube resolution, interval, completed refresh count, CPU submission timings, pending state and texture identity. These CPU timings do not measure GPU execution.

Rendering and lifetime

Each instance owns a private scene with one sky mesh, a half-float cube target, capture material and persistent PMREM generator. The bundled Three.js r184 supports fromCubemap(texture, existingTarget), so both the cube target and PMREM output are reused. Terrain, objects, shadow lights and application reflection callbacks never enter the capture scene. Unchanged frames do not allocate PMREM resources or perform captures.

The capture branch writes linear-sRGB radiance without display tone mapping, exposure or output transfer. The main sky retains its display transform. Sun and moon emission vanish below the horizon; cloud transmission and storm attenuation also affect their visible emission. In sky-IBL mode, brightness is encoded in the environment radiance and scene.environmentIntensity remains 0.65, avoiding a second night attenuation. The ambient hemisphere follows the sky colors.

Capture restores the previous render target/cube face/mipmap, XR flag, tone mapping, exposure and clear behavior in finally. Output color space, scene visibility and the displayed sky material are not mutated. Restoring the render target restores its viewport and scissor state; the code does not overwrite an offscreen target viewport with canvas dimensions. Disposal releases capture resources and restores prior scene environment/intensity/fog while those properties are still owned by the instance.

Verification

Open the focused fixture through the local server and select Run focused checks. Chrome inspection on 2026-09-07 passed 11/11 cases, including IBL-only PBR pixel comparisons at noon/sunset/night, cloud response, HDR/exposure invariance, below-horizon emitters, target reuse, paused refresh, renderer state restoration after injected failure, separate instances and three disposal cycles. No unexpected runtime or shader errors were reported. Recorded results.

At 64 px per cube face, five warm captures submitted 23 draw calls each. GPU timer-query samples were 13.35, 3.58, 3.40, 3.26 and 3.37 ms: median 3.40 ms, maximum 13.35 ms. CPU submission was 0.1–0.6 ms. The default schedule permits at most one ordinary refresh per two seconds. An initial earlier fixture revision incurred about 538 ms of first-use shader/setup work; the final revision was tested with a warm shader cache, so these measurements do not establish its cold-start time.

Three hundred unchanged updates took 0.2 ms in aggregate and performed zero captures; individual measurements were below the browser timer's useful resolution. GPU texture/geometry counts returned to the pre-test baseline after every construction/disposal cycle. These are focused fixture measurements on the inspected browser, not full-world frame-rate guarantees. Sky-only IBL does not contain nearby buildings, ground geometry or object reflections, and the low-resolution capture intentionally limits sharp reflection detail.

Twilight visibility refinement

The twilight1 shader delays the star field until the sun is below the bright-twilight range and delays the galactic band further. Individual stars have varying brightness and subtle color variation. This corrects the dense bright stars previously visible over the orange sky at 18:15. The artistic time/direction model is unchanged; the shader is not an astronomical catalogue or a calibrated atmospheric simulation.

tests/sky-twilight.html passed 5/5 actual-shader checks in confirmed Chrome. Noon and 18:15 produced zero star contribution; stars appeared at 18:45 and night preserved the galactic band. The difference metric includes the band, so its changed-pixel count is not a count of stars. See verification/sky-twilight-summary.json. Root also inspected the bright-twilight and midnight images directly.