| name | threejs-scalable-real-time-shadows |
| description | 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. |
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.