Documentation
    Preparing search index...

    Interface SceneLights

    The frame's flattened lighting (§68), as a backend consumes it. One mutable record, rewritten in place by every collectSceneLights call — the pooling policy every per-frame structure in this package follows (plan D7). Copy anything you need to keep.

    interface SceneLights {
        ambientColor: [number, number, number];
        direction: Vector3;
        directionalColor: [number, number, number];
        hasDirectionalLight: boolean;
        hasHemisphereLight: boolean;
        hasShadow: boolean;
        hemisphereGround: [number, number, number];
        hemisphereSky: [number, number, number];
        hemisphereUp: Vector3;
        punctualColors: Float32Array;
        punctualCount: number;
        punctualDirections: Float32Array;
        punctualParams: Float32Array;
        punctualPositions: Float32Array;
        shadowBias: number;
        shadowMapSize: number;
        shadowMatrix: Matrix4;
        shadowNormalBias: number;
    }
    Index
    ambientColor: [number, number, number]

    The scene ambient term; [0, 0, 0] when the root offers none.

    direction: Vector3

    World-space unit vector the light travels along; (0, 0, -1) — the direction an unrotated light shines — when there is no light. Owned by the record and rewritten by the next collection.

    directionalColor: [number, number, number]

    The light's color premultiplied by its intensity — the one product every shaded fragment needs, computed once per frame here rather than per fragment there. [0, 0, 0] when there is no light, which makes the Lambert term vanish and is why a backend needs no shader variant for the unlit-scene case.

    hasDirectionalLight: boolean

    Whether a directional light was found. When false the other two directional fields hold their documented no-light values, so a backend may upload them unconditionally.

    hasHemisphereLight: boolean

    Whether a hemisphere light was found. When false the three hemisphere fields hold their documented no-light values (zeros), so a backend may upload them unconditionally — or, on GL, upload nothing at all: a bool uniform's initial value is false, the same compatibility contract SceneLights.punctualCount states.

    hasShadow: boolean

    Whether the frame's directional light casts a shadow map (§69; R-18, 2026-08-09) — true only when that light exists, sets castShadow, and offers a computeShadowMatrix.

    A backend that sees false here must issue no shadow call at all — not a framebuffer bind, not a uniform, not a texture. That is the same compatibility contract SceneLights.punctualCount states, kept the same way: a bool uniform's initial value in GL is false, so a scene lit the way every scene was lit before §69 shipped emits byte-for-byte the GL sequence it always did.

    hemisphereGround: [number, number, number]

    Ground colour premultiplied by intensity. [0, 0, 0] when there is no hemisphere.

    hemisphereSky: [number, number, number]

    Sky colour premultiplied by intensity. [0, 0, 0] when there is no hemisphere, which makes the mix vanish.

    hemisphereUp: Vector3

    World-space unit vector toward the sky; (0, 1, 0) — §7a's world up, the direction an unrotated hemisphere faces — when there is no hemisphere. Owned by the record and rewritten by the next collection.

    The no-light up is the identity axis rather than zero so a backend that uploads unconditionally still has a finite, documented vector; the zero sky and ground are what make the mix contribute nothing.

    punctualColors: Float32Array

    Each light's colour premultiplied by its intensity, r, g, b per light — the same product directionalColor carries, for the same reason (computed once per frame here rather than per fragment there).

    punctualCount: number

    How many entries of the four arrays below are live — 0 through MAX_PUNCTUAL_LIGHTS (R-17, 2026-08-09).

    A backend that sees 0 here must upload nothing at all. That is not an optimisation, it is the compatibility contract: an int uniform's initial value in GL is 0, so a scene lit the way every scene was lit before this field existed issues byte-for-byte the GL sequence it always did — the same technique R-38's permissive layer mask and R-15's bool encode switch used.

    punctualDirections: Float32Array

    Each light's cone axis, x, y, z per light: the world-space unit vector the light travels along, matching SceneLights.direction's convention. A point light has no axis and writes (0, 0, 0).

    punctualParams: Float32Array

    Four scalars per light, packed so the fragment stage needs no branching on a light kind beyond one comparison:

    x  range, or 0 for unbounded — the KHR_lights_punctual window
    y  cos(outerConeAngle);              0 for a point light
    z  1 / max(cos inner − cos outer, 1e-6);   0 for a point light
    w  1 for a spot light, 0 for a point light
    

    The reciprocal in z is precomputed here, once per light per frame, rather than in the shader once per light per fragment; the max is what makes an inner ≥ outer cone a hard edge instead of a division by zero (see @fourjs/scene's SpotLight).

    punctualPositions: Float32Array

    World-space positions, x, y, z per light, MAX_PUNCTUAL_LIGHTS entries long and only the first punctualCount of them meaningful.

    Float32Array rather than the number[] the fields above use, because these are uploaded as a block: a backend hands the array straight to uniform3fv with no per-frame copy into scratch, which is the whole reason the packing is done here instead of there. The trailing entries are zeroed by every collection so a transcript of the upload is deterministic rather than showing the previous frame's leftovers.

    shadowBias: number

    Constant depth bias, in clip-space depth units (§69); 0 when nothing casts.

    shadowMapSize: number

    Edge of the square shadow map in texels; 0 when nothing casts. The backend allocates its off-screen depth surface at this size and derives the PCF tap offset (1 / shadowMapSize) from it.

    shadowMatrix: Matrix4

    World space → shadow-map clip space (§69), written by the casting light's own computeShadowMatrix. Identity when SceneLights.hasShadow is false, and owned by the record — read it, upload it, do not mutate it.

    shadowNormalBias: number

    Normal-space bias, in metres (§69, §40); 0 when nothing casts.