Skip to content

Commit 0f9c987

Browse files
committed
fix(soba): replace sampler2DShadow with sampler2D for PCSS in Three.js r182+
1 parent 49928ef commit 0f9c987

File tree

1 file changed

+47
-28
lines changed

1 file changed

+47
-28
lines changed

libs/soba/misc/src/lib/soft-shadows.ts

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ vec2 pcssVogelDiskSample(int sampleIndex, int sampleCount, float angle) {
188188
return vec2(cosine, sine) * r;
189189
}
190190
191-
float penumbraSize( const in float zReceiver, const in float zBlocker ) {
191+
float pcssPenumbraSize( const in float zReceiver, const in float zBlocker ) {
192192
return (zReceiver - zBlocker) / zBlocker;
193193
}
194194
195-
float findBlocker(sampler2D shadowMap, vec2 uv, float compare, float angle) {
195+
float pcssFindBlocker(sampler2D shadowMap, vec2 uv, float compare, float angle) {
196196
float texelSize = 1.0 / float(textureSize(shadowMap, 0).x);
197197
float blockerDepthSum = float(${focus});
198198
float blockers = 0.0;
@@ -241,32 +241,16 @@ float PCSS (sampler2D shadowMap, vec4 coords, float shadowIntensity) {
241241
vec2 uv = coords.xy;
242242
float zReceiver = coords.z;
243243
float angle = highPassRandRGB(gl_FragCoord.xy).r * PI2;
244-
float avgBlockerDepth = findBlocker(shadowMap, uv, zReceiver, angle);
244+
float avgBlockerDepth = pcssFindBlocker(shadowMap, uv, zReceiver, angle);
245245
if (avgBlockerDepth == -1.0) {
246246
return 1.0;
247247
}
248-
float penumbraRatio = penumbraSize(zReceiver, avgBlockerDepth);
248+
float penumbraRatio = pcssPenumbraSize(zReceiver, avgBlockerDepth);
249249
float shadow = pcssVogelFilter(shadowMap, uv, zReceiver, 1.25 * penumbraRatio, angle);
250250
return mix( 1.0, shadow, shadowIntensity );
251251
}`;
252252
}
253253

254-
/**
255-
* Generates the replacement getShadow function for r182+
256-
*/
257-
function getShadowReplacement(): string {
258-
return `float getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {
259-
shadowCoord.xyz /= shadowCoord.w;
260-
shadowCoord.z += shadowBias;
261-
bool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;
262-
bool frustumTest = inFrustum && shadowCoord.z <= 1.0;
263-
if ( frustumTest ) {
264-
return PCSS( shadowMap, shadowCoord, shadowIntensity );
265-
}
266-
return 1.0;
267-
}`;
268-
}
269-
270254
function reset(gl: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera): void {
271255
scene.traverse((object) => {
272256
if ((object as THREE.Mesh).material) {
@@ -304,17 +288,52 @@ export class NgtsSoftShadows {
304288
const original = THREE.ShaderChunk.shadowmap_pars_fragment;
305289

306290
if (version >= 182) {
307-
// Three.js r182+ uses native depth textures and has a different shader structure
308-
// We need to replace the getShadow function entirely
291+
// Three.js r182+ uses native depth textures and has a different shader structure.
292+
// The PCF path uses sampler2DShadow, but PCSS needs sampler2D for manual depth comparison.
293+
// We inject our PCSS code and replace the BASIC shadow type's getShadow function,
294+
// then also replace the PCF uniform declarations to use sampler2D instead of sampler2DShadow.
309295
const pcssCode = pcssModern(options);
310296

311-
// Find and replace the PCF getShadow function
312-
const getShadowRegex =
313-
/(#if defined\( SHADOWMAP_TYPE_PCF \)\s+float getShadow\( sampler2DShadow shadowMap[^}]+\})/s;
297+
let shader = THREE.ShaderChunk.shadowmap_pars_fragment;
298+
299+
// 1. Inject PCSS functions after USE_SHADOWMAP
300+
shader = shader.replace('#ifdef USE_SHADOWMAP', '#ifdef USE_SHADOWMAP\n' + pcssCode);
301+
302+
// 2. Replace sampler2DShadow with sampler2D for directional lights (PCF path)
303+
shader = shader.replace(
304+
/#if defined\( SHADOWMAP_TYPE_PCF \)\s+uniform sampler2DShadow directionalShadowMap\[ NUM_DIR_LIGHT_SHADOWS \];/,
305+
`#if defined( SHADOWMAP_TYPE_PCF )
306+
uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];`,
307+
);
308+
309+
// 3. Replace sampler2DShadow with sampler2D for spot lights (PCF path)
310+
shader = shader.replace(
311+
/#if defined\( SHADOWMAP_TYPE_PCF \)\s+uniform sampler2DShadow spotShadowMap\[ NUM_SPOT_LIGHT_SHADOWS \];/,
312+
`#if defined( SHADOWMAP_TYPE_PCF )
313+
uniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];`,
314+
);
315+
316+
// 4. Replace the PCF getShadow function to use our PCSS
317+
// Match from the function signature to its closing brace
318+
const getShadowPCFRegex =
319+
/(#if defined\( SHADOWMAP_TYPE_PCF \)\s+float getShadow\( sampler2DShadow shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord \) \{[\s\S]*?return mix\( 1\.0, shadow, shadowIntensity \);\s*\})/;
320+
321+
shader = shader.replace(
322+
getShadowPCFRegex,
323+
`#if defined( SHADOWMAP_TYPE_PCF )
324+
float getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {
325+
shadowCoord.xyz /= shadowCoord.w;
326+
shadowCoord.z += shadowBias;
327+
bool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;
328+
bool frustumTest = inFrustum && shadowCoord.z <= 1.0;
329+
if ( frustumTest ) {
330+
return PCSS( shadowMap, shadowCoord, shadowIntensity );
331+
}
332+
return 1.0;
333+
}`,
334+
);
314335

315-
THREE.ShaderChunk.shadowmap_pars_fragment = THREE.ShaderChunk.shadowmap_pars_fragment
316-
.replace('#ifdef USE_SHADOWMAP', '#ifdef USE_SHADOWMAP\n' + pcssCode)
317-
.replace(getShadowRegex, `#if defined( SHADOWMAP_TYPE_PCF )\n\t\t${getShadowReplacement()}`);
336+
THREE.ShaderChunk.shadowmap_pars_fragment = shader;
318337
} else {
319338
// Three.js < r182 uses RGBA-packed depth
320339
THREE.ShaderChunk.shadowmap_pars_fragment = THREE.ShaderChunk.shadowmap_pars_fragment

0 commit comments

Comments
 (0)