Scalable Real-Time Shadows

Fit scalable directional cast shadows in Three.js r185 WebGPU/TSL. Use when choosing one bounded shadow, CSM, tiled arrays, or cached clipmaps; stabilizing projection, filtering, or bias; or fixing invalidation, caster parity, bindings, or sustained cost.

Image: Shadow architecture bench hardware WebGPU readback. Source lab: webgpu-shadow-architecture-bench.

$threejs-scalable-real-time-shadows 3 primary targets 4 flagships 1 secondary surface accepted runtime evidence Latest skill update commit 2ce13bc ↗ SKILL.md on GitHub ↗ raw (for agents) ↗

The approach, mathematically

Shadow scale is a budget problem: texel density where the camera looks, amortization everywhere else. Cascade splits blend logarithmic and uniform schemes:

$$z_i = \lambda\, z_n\Big(\tfrac{z_f}{z_n}\Big)^{i/N} + (1-\lambda)\Big(z_n + \tfrac{i}{N}(z_f - z_n)\Big)$$

Texel stabilization snaps the light-space origin to whole texels so shadows don't shimmer under camera motion:

$$\mathbf o' = \Big\lfloor \frac{\mathbf o}{\Delta_{texel}} \Big\rfloor \Delta_{texel}, \qquad \Delta_{texel} = \frac{2\,r_{cascade}}{N_{shadowmap}}$$

Cached clipmaps re-render a level only when its content or coverage is invalidated — the per-frame cost is the sum over dirty levels, not all levels. The parity contract: any vertex displacement in the visible pass must run identically in the caster pass (castShadowPositionNode), or silhouettes and shadows disagree.

Preview and evidence ledger

Every image identifies what it proves. Page screenshots demonstrate the published presentation only; generated inputs demonstrate asset channels only; rendering acceptance still requires same-lab readback and a schema-v2 bundle.

Accepted runtime evidence available20 published images
Native WebGPU runtime evidence preview

WebGPU Directional Shadow Architecture Bench

Accepted
visualCorrectness
PASS
mechanismCorrectness
PASS
performanceCompliance
NOT_CLAIMED
gpuAttribution
NOT_CLAIMED
lifecycleStability
PASS
visualError
PASS
  • Correctness and CDP Chrome hardware physical-route visual review pass on the current source closure.
  • Named-adapter GPU timestamp timing remains NOT_CLAIMED.
Native WebGPU runtime evidence preview

WebGPU Cached Shadow Pipeline Integration

Accepted
visualCorrectness
PASS
mechanismCorrectness
PASS
performanceCompliance
NOT_CLAIMED
gpuAttribution
NOT_CLAIMED
lifecycleStability
PASS
visualError
PASS
  • Correctness and CDP Chrome hardware physical-route visual review pass on the current source closure.
  • Named-adapter GPU timestamp timing remains NOT_CLAIMED.

The full skill

The complete SKILL.md as loaded by agents, rendered verbatim.

Scalable Real-Time Shadows

Directional shadows are a fit problem: fit the smallest light-space representation that covers required caster-to-receiver rays, then spend work on the projection, filter, and updates receivers actually use.

$threejs-choose-skills is an optional multi-system coordinator. Use $threejs-visual-validation for capture/readback evidence.

1. Choose one topology

Compare the same seeded workload, projection, filter, and receiver policy:

Gate First r185 WebGPU branch Cost topology
A bounded receiver/caster volume passes world-texel and depth-precision gates one DirectionalLight shadow one view, one depth texture, one filter evaluation
Camera-depth coverage changes continuously CSMShadowNode L shadow views/maps; normally one active filter, two in a fade
One fixed orthographic footprint is partitioned spatially TileShadowNode one depth array, N backend layer passes, N containment branches, and a union render list that can draw each caster in every layer
Very large coverage persists and measured reuse survives invalidation custom cached clipmap L persistent levels; selected updates; L portable filters or one/two target-proven array/atlas filters
Casters deform or change broadly each frame one fitted shadow, CSM, or a same-light static/dynamic split Cache invalidation approaches full redraw.

Core and Tile shadow targets retain color-bearing attachments beside depth; VSM adds distribution/blur resources and passes. TileShadowNode updates all layers together and is not a persistent tile cache.

Classify consequential numbers as Derived from bounds/equations, Authored intent, Gated limits, or target Measured evidence.

Complete when: one topology is selected from measured draw/raster/receiver/ binding/reuse pressure, every rejected branch has a failed gate, and no copied map size, level count, bias, or millisecond promise drives the choice.

2. Fit projection, texels, filtering, and bias

  • Fit XY to required biased receiver coordinates plus filter, snap, and coverage guards. Fit Z independently to receivers and every occluder on their light rays; XY texel width does not define a valid Z quantum.
  • Use one stable directional-light basis and global/light anchor. Snap each committed center to its fixed dx/dy grid with one deterministic quantizer. A basis/sign/anchor change starts a new content epoch.
  • Derive filter support from the actual Basic/PCF/PCFSoft/VSM implementation and backend. Reserve that support inside containment and match world footprints across blends.
  • Treat normalBias as a world-space normal offset and bias/biasNode as a normalized comparison-depth offset. Derive and validate them separately.

When stabilizing crawl, seams, acne, or peter-panning, read projection, filter, and bias mechanics.

Complete when: slow sub-texel motion is stable; every XY/Z boundary remains covered; filter support cannot escape its domain; and front-facing, grazing, thin-contact, and cross-level fixtures pass under the selected depth convention.

3. Gate the exact r185 implementation

Initialize WebGPU before inspecting capabilities:

await renderer.init();
if ( renderer.backend.isWebGPUBackend !== true ) {
  throw new Error( 'Directional shadows require WebGPU.' );
}

Attach a custom node through light.shadow.shadowNode before the affected material/light graphs build. A runtime change requires every affected graph and pipeline to rebuild and pass disposal/recreation.

When using core, CSM, Tile, reversed depth, or custom shadow hooks, read the r185 source gates for clone omissions, depth comparisons, layer behavior, transform conventions, and lifecycle caveats.

Complete when: revision/imports, backend/depth policy, camera ownership, shadow target type, layer behavior, material bind layout, and every selected built-in caveat have a fixed-scene diagnostic or a rejected configuration.

4. Build an explicit custom cache only when selected

Each level owns distinct state:

desired  = next coverage request
rendered = center/range/content encoded into an inactive target
committed = exact valid target and parameters sampled by receivers
dirty    = desired coverage differs, while committed content may remain valid
invalid  = committed depth no longer represents required content

Receivers sample committed state only. Invalid levels contribute zero weight; the next valid coarser level inherits the remainder, and unresolved outer coverage is lit. Correctness invalidations outrank age/quality refreshes.

Freeze desired center, depth interval, basis epoch, and content epoch; render; restore renderer state; then atomically commit center, interval, target/layer, matrix, and rendered epoch. Work encoded after the current presentation seal uses an inactive resource generation and commits to the next presentation; the prior committed generation remains immutable until its consumers finish.

When a custom cache is selected, read the cache state, scheduling, and sampling contract before implementing it.

Complete when: rendered metadata matches its inactive target; committed metadata matches the resource receivers sample; desired state differs only while committed coverage remains valid; first-use state is invalid; failed updates preserve only still-correct commits; late updates cannot mutate a sampled generation; and every invalid level is excluded until repaired.

5. Invalidate swept causes and preserve caster parity

Track light basis; caster previous/current transform; geometry/LOD; visibility, layer, and cast flags; alpha/mask/coverage; deformation envelope; asset/chunk generation; and resource identity. Union or sweep previous/current conservative caster silhouettes so vacated and newly occupied shadow depths both invalidate.

A central change can intersect every nested full map. When that redraw is too expensive, use non-overlapping ownership, paged residency, proxies, or a dynamic overlay inside the same light/shadow node. A second directional light would duplicate illumination.

Visible and shadow passes share local-space deformation and alpha/coverage. positionNode and castShadowPositionNode are local-space hooks; receivedShadowPositionNode is world-space receiver lookup and is validated separately.

For the invalidation event table, swept receiver influence, and parity contract, read swept invalidation and caster parity.

When another system supplies light/caster motion, rebasing, or presentation cadence, bind units, coordinate frame, current/previous sample times, authority, version, resource generation, validity, staleness, and reset conditions before use. The shadow cache remains sole writer of its desired, dirty, invalid, rendered, and committed state.

Complete when: every content dependency maps to redraw, sampling-only update, or no-op; every moved/deformed caster invalidates its swept old/new influence; and visible/caster silhouette parity passes for morphing, skinning, instancing, batching, sidedness, layers, and alpha coverage.

6. Validate sustained behavior and lifecycle

Capture committed coverage/weights, light depth, filter support, bias, invalid fallback, caster parity, binding layout, target inventory, and fixed final views. Exercise camera crawl; all XY/Z boundaries; cross-level fades; localized and broad invalidation; light-basis changes; resize/DPR; multi-camera use; update spikes/debt; and dispose/recreate.

Measure warmed, thermal-steady CPU/GPU frame quantiles, shadow view draws and triangles, vertex/raster/alpha work, receiver filter evaluations, traversal, allocations, bindings, invalid coverage, and update spikes. Optimize the measured bottleneck rather than a topology proxy.

For binding/resource inventory, workload equations, and acceptance gates, read binding, workload, and validation.

Complete when: rendered output matches the selected baseline and diagnostic causes; stale invalid content is never sampled; adapter and product headroom gates pass; sustained frame and spike gates pass; and detaching the node, targets/textures, cloned shadows/lights, storage, listeners, and debug resources returns counters to baseline.

Ownership

This skill owns directional shadow projection, filtering/bias, caster parity, cache state/invalidation, receiver sampling, and shadow diagnostics. The scene owner supplies caster/light inputs; the pipeline owner controls shared output and temporal-radiance rejection caused by changed shadow content.

Secondary provider surfaces

Preserved concept proxies and generated-asset previews. They are excluded from primary completion counts and link to the canonical lab through the schema-v2 registry.