Variable SHADOW_GLSLConst
SHADOW_GLSL: "uniform bool useShadow;\nuniform sampler2D shadowMap;\nuniform mat4 shadowMatrix;\nuniform float shadowBias;\nuniform float shadowNormalBias;\nuniform float shadowTexelSize;\n\nfloat shadowFactor(vec3 worldPosition, vec3 n) {\n vec4 lightSpace = shadowMatrix * vec4(worldPosition + n * shadowNormalBias, 1.0);\n vec3 c = (lightSpace.xyz / lightSpace.w) * 0.5 + 0.5;\n if (any(lessThan(c, vec3(0.0))) || any(greaterThan(c, vec3(1.0)))) {\n return 1.0;\n }\n float receiver = c.z - shadowBias;\n float lit = 0.0;\n for (int y = -1; y <= 1; y += 1) {\n for (int x = -1; x <= 1; x += 1) {\n float occluder = texture(shadowMap, c.xy + vec2(x, y) * shadowTexelSize).r;\n lit += receiver <= occluder ? 1.0 : 0.0;\n }\n }\n return lit / 9.0;\n}\n" = ...
The receiver chunk both shaded fragment stages splice in (§69; R-18, 2026-08-09) — the shared half of shadowing, exactly as PUNCTUAL_LIGHT_GLSL is the shared half of the light set.
Four properties are load-bearing and each is a decision:
1.0. Oneany(lessThan) || any(greaterThan)covers all three axes, which is both shorter and exactly the claim. An under-sizedextentthen reads as "shadows stop here", which an author can see and fix, rather than as "the world beyond the box went black".wis 1). It costs one division per fragment and is what lets §69's spot-light shadows reuse this chunk unchanged when they land.shadowBiasis subtracted from the receiver rather than added to the caster, so the sign convention matches every other engine's: a positive bias moves the surface towards the light.Why explicit taps and not hardware PCF
GLES 3.00 offers
sampler2DShadowwithTEXTURE_COMPARE_MODE, which gives a free 2×2 comparison-filtered tap. This tier uses a plainsampler2DwithNEARESTfiltering and nine explicit taps instead:gl-render-target.tsallocates — coupling that cache to what a particular consumer intends to do with a depth texture, which is exactly the coupling its design avoids;The loop bounds are constants, so the nine taps unroll; the tap offset arrives as
shadowTexelSize = 1 / mapSize, computed once per frame on the CPU rather than once per fragment here.