Documentation
    Preparing search index...

    Class DirectionalLightShadow

    How a DirectionalLight casts (§69) — the shadow-map resolution, the two bias controls, and the orthographic volume the map covers.

    const sun = new DirectionalLight({ intensity: 3, castShadow: true });
    sun.shadow.mapSize = 2048;
    sun.shadow.extent = 12; // a 24 m × 24 m box about the light's position
    sun.shadow.bias = 0.002;

    Every property is a validated accessor (the F14 policy, 2026-08-07): the constructor's §85 check runs again on every write, so a NaN map size is a RangeError at the assignment that caused it rather than a black frame three systems later. Nothing here is clamped or reordered — WP-3.3's no-silent-rewrites rule — so an unusable value is refused, never quietly repaired.

    A directional light has no position as far as lighting is concerned (§68 — see Node.getWorldDirection), but a shadow map is a rendering of a bounded volume and something has to bound it. This tier takes the volume from the light's own node: the map is rendered from the light's world position, looking along its −Z axis, through an orthographic box 2 × extent wide and tall with the near and far planes below. Moving or aiming the light node therefore moves the shadow volume, which is the one control an author already has in their hands.

    The alternative — fitting the box to the scene's bounds every frame — is not this tier's, and deliberately: it needs a world-space bounds pass this engine does not yet run (§87's culling packet owns it), and a volume that silently resizes makes shadow texel density frame-dependent, which is the shimmer §33's determinism language exists to keep out of a recorded run.

    §69 lists ten features; this settings object is sized for the one tier that ships. Named here rather than sketched, because each is public shape:

    • cascaded shadow maps — one map, one volume. Cascades need a split scheme, a map per split, and a per-fragment cascade selection, and they are what make a large outdoor volume workable; extent is the honest single-cascade knob until then.
    • point-light cubemap shadows and spot-light shadows — neither PointLight nor SpotLight carries castShadow at all (see the module header). A cube map needs six passes and a samplerCube; a spot needs a perspective shadow matrix and a second map slot, which is the shadow-atlas item below.
    • shadow atlas management — one map, one caster light. An atlas is what turns "a per-light shadow index" into something a bounded uniform set can address.
    • transparent shadow masks — the depth-only pass writes geometry, not alpha, so a sprite or a cut-out texture casts nothing here (§55 quads are skipped by the caster pass entirely rather than casting a hard rectangle).
    • contact shadows — a screen-space effect, and so §70's business rather than this record's.
    Index
    • get bias(): number

      Constant depth offset subtracted from a receiver's own depth before it is compared against the map (§69 "bias"), in clip-space depth units (0…1 across the near-to-far range). Defaults to 0.0015.

      Too small and a lit surface shadows itself in stripes (acne); too large and a shadow detaches from its caster (peter-panning). The right value depends on extent, far, and the map's resolution, which is exactly why §69 makes it an author's control rather than a constant.

      Any finite number is accepted, negative ones included: a negative bias is a deliberate over-occlusion an author may want, and refusing it would be the silent rewrite WP-3.3 rules out.

      Returns number

    • set bias(value: number): void

      Parameters

      • value: number

      Returns void

    • get extent(): number

      Half-width and half-height of the orthographic shadow volume, in metres (§40). Defaults to 10, i.e. a 20 m × 20 m box. Must be finite and positive.

      See the class header for what the volume is centred on and why it is authored rather than fitted to the scene.

      Returns number

    • set extent(value: number): void

      Parameters

      • value: number

      Returns void

    • get far(): number

      Distance in front of the light at which the shadow volume ends, in metres (§40). Defaults to 100. Must be finite, positive, and greater than DirectionalLightShadow.near.

      A caster beyond far, or nearer than near, is simply not in the map; a receiver outside the volume is fully lit rather than fully shadowed (see @fourjs/render-webgl's shadow chunk), which is the choice that makes an under-sized volume read as "shadows stop here" rather than as "the world went black".

      Returns number

    • set far(value: number): void

      Parameters

      • value: number

      Returns void

    • get mapSize(): number

      Edge length of the square shadow map in texels (§69 "configurable resolution"). Defaults to 1024.

      A finite integer of at least 1; anything else is refused (§85). It is not required to be a power of two — the map is sampled with NEAREST filtering and never mipmapped, so WebGL 2's non-power-of-two rules do not bite — but a device's MAX_TEXTURE_SIZE still does, and a size past it makes the backend's framebuffer incomplete, which skips the shadow rather than the frame (gl-render-target.ts).

      Returns number

    • set mapSize(value: number): void

      Parameters

      • value: number

      Returns void

    • get normalBias(): number

      Distance, in metres (§40), that a receiver is pushed along its own surface normal before its shadow-map coordinate is computed (§69 "normal-bias"). Defaults to 0 — off.

      The other half of the acne fix, and the half that scales with geometry rather than with depth precision: a slope facing away from the light needs an offset proportional to its own size, which a constant depth bias cannot express. Must be finite and non-negative — a negative normal bias would pull the receiver into its own caster, which is not a look, it is the artefact.

      Returns number

    • set normalBias(value: number): void

      Parameters

      • value: number

      Returns void