shadowSDF2D
shadowSDF2D(
surfaceWorldPos,lightWorldPos,sdfTexture,worldSize,worldOffset,options?):Node<"float">
Defined in: packages/nodes/src/lighting/shadows.ts:271
Sphere-trace a 2D hard shadow ray through an SDF texture.
Walks along the line from the shaded surface point toward the light,
sampling the SDF at each step to advance by the guaranteed-clear
distance. Binary result: 0 if the ray hits an occluder along the
way, 1 if it reaches the light cleanly. Soft shadow edges come
from (a) the separable gaussian blur pass applied in SDFGenerator
and (b) the IQ soft-shadow accumulation along the trace — primarily,
not from texture filtering. The SDF sample filter is selectable via
shadowFilter (auto|nearest|linear): nearest for crisp / pixel-
snapped shadows (avoids the eps-threshold halo), linear for smoother
non-snapped edges. auto picks nearest when shadow pixel-snap is on,
linear otherwise. Either way the blur + penumbra math carry the bulk
of the softness.
The classic IQ penumbra term min(k · h / t) was removed because
it accumulates at every step of the walk, which in closed 2D scenes
(dungeons, corridors — casters scattered everywhere) produces a
uniform global darkening: every ray walks near something, so
h/t always drops below 1 somewhere, shadow always ends below 1,
and shadowStrength linearly amplifies that scene-wide. The
softness option is retained for API stability (now ignored); a
future PCSS-style two-phase trace could reintroduce real
distance-aware penumbra widening without the false-proximity issue.
The SDF texture is assumed to be produced by SDFGenerator and
encodes SIGNED world-space distance on the .r channel — negative
inside occluders, positive outside. Signed distance lets the trace
detect “stepped into an occluder” mid-walk without needing the
hardcoded caster-escape offset the unsigned variant required. World-
space distances keep the sphere-trace isotropic on non-square
viewports. worldSize / worldOffset are still consumed here to
transform the fragment/ray world position into the SDF’s UV space for
sampling.
Parameters
Section titled “Parameters”surfaceWorldPos
Section titled “surfaceWorldPos”Node <"vec2">
World-space position of the shaded fragment.
lightWorldPos
Section titled “lightWorldPos”Node <"vec2">
World-space position of the light.
sdfTexture
Section titled “sdfTexture”SDF texture captured at build time. Must come
from SDFGenerator (UV-space distances in .r).
worldSize
Section titled “worldSize”Node <"vec2">
Camera frustum size (Node — uniform, updated each frame from the camera bounds).
worldOffset
Section titled “worldOffset”Node <"vec2">
Camera frustum offset (Node — uniform).
options?
Section titled “options?”World-space hit threshold. Default 0.5.
fragmentCastsShadow?
Section titled “fragmentCastsShadow?”Node <"bool">
When provided, gates the nearCaster escape path: the ray only
skips past an occluder it’s sitting on if THIS fragment is itself
a shadow caster. Without this gate, a floor fragment that happens
to lie under a sprite’s rasterized silhouette (seeded in the SDF)
would incorrectly escape and render as lit — leaving a bright
alpha-blended halo wherever a sprite’s anti-aliased edge overlaps
the floor. Pass readCastShadowFlag() here from the caller’s
light shader.
maxShadowDistance?
Section titled “maxShadowDistance?”Maximum world-space distance from the receiver at which shadow
still applies. A ray that hits an occluder at t = t_hit has
its shadow scaled by 1 - t_hit / maxShadowDistance, clamped to
[0, 1]. Default 0 means no distance falloff (shadow is binary at
every distance). Set >0 to hide point-light cone-fan artifacts
far from the caster — close-range shadows stay solid, long-range
shadows fade to lit.
softness?
Section titled “softness?”Retained for API stability; currently ignored. Soft edges come from SDF blur + linear sampling, not per-ray integration. Will re-enable once a PCSS-style trace lands.
startOffset?
Section titled “startOffset?”World-space distance to push the ray origin forward when the fragment itself sits inside a caster silhouette (signed SDF < 0). Must clear the caster’s radius — too small and the first samples land inside the caster (self-shadow) or in the Voronoi-seam zone near the silhouette (shadow-edge ringing). Default 40 world units matches the old unsigned-SDF escape calibration; scenes with smaller/larger casters should tune accordingly.
steps?
Section titled “steps?”number
Compile-time loop count. Default 32.
Returns
Section titled “Returns”Node <"float">
Node<‘float’> in [0, 1]. 0 = fully shadowed, 1 = fully lit.