From a8431dcb73cf8539554022764a59897998c9a455 Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Tue, 4 Nov 2025 23:10:23 +0900 Subject: [PATCH 1/3] WebGLRenderer: Use Vogel disk sampling for PCF shadows. --- .../shadowmap_pars_fragment.glsl.js | 91 +++++++++++-------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js index fc8ba6af766c91..8aca993aacc6ae 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js @@ -65,6 +65,27 @@ export default /* glsl */` #endif + float interleavedGradientNoise( vec2 position_screen ) { + + vec3 magic = vec3( 0.06711056, 0.00583715, 52.9829189 ); + return fract( magic.z * fract( dot( position_screen, magic.xy ) ) ); + + } + + vec2 vogelDiskSample( int sampleIndex, int samplesCount, float phi ) { + + const float goldenAngle = 2.399963229728653; + + float r = sqrt( float( sampleIndex ) + 0.5 ) / sqrt( float( samplesCount ) ); + float theta = float( sampleIndex ) * goldenAngle + phi; + + float sine = sin( theta ); + float cosine = cos( theta ); + + return vec2( cosine, sine ) * r; + + } + float texture2DCompare( sampler2D depths, vec2 uv, float compare ) { float depth = unpackRGBAToDepth( texture2D( depths, uv ) ); @@ -132,34 +153,22 @@ export default /* glsl */` vec2 texelSize = vec2( 1.0 ) / shadowMapSize; - float dx0 = - texelSize.x * shadowRadius; - float dy0 = - texelSize.y * shadowRadius; - float dx1 = + texelSize.x * shadowRadius; - float dy1 = + texelSize.y * shadowRadius; - float dx2 = dx0 / 2.0; - float dy2 = dy0 / 2.0; - float dx3 = dx1 / 2.0; - float dy3 = dy1 / 2.0; + float phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2; shadow = ( - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z ) - ) * ( 1.0 / 17.0 ); + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 0, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 1, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 2, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 3, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 4, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 5, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 6, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 7, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 8, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 9, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 10, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 11, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + ) * ( 1.0 / 12.0 ); #elif defined( SHADOWMAP_TYPE_PCF_SOFT ) @@ -307,19 +316,27 @@ export default /* glsl */` #if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM ) - vec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y; + float phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2; + + vec2 d0 = vogelDiskSample( 0, 8, phi ) * shadowRadius * texelSize.y; + vec2 d1 = vogelDiskSample( 1, 8, phi ) * shadowRadius * texelSize.y; + vec2 d2 = vogelDiskSample( 2, 8, phi ) * shadowRadius * texelSize.y; + vec2 d3 = vogelDiskSample( 3, 8, phi ) * shadowRadius * texelSize.y; + vec2 d4 = vogelDiskSample( 4, 8, phi ) * shadowRadius * texelSize.y; + vec2 d5 = vogelDiskSample( 5, 8, phi ) * shadowRadius * texelSize.y; + vec2 d6 = vogelDiskSample( 6, 8, phi ) * shadowRadius * texelSize.y; + vec2 d7 = vogelDiskSample( 7, 8, phi ) * shadowRadius * texelSize.y; shadow = ( - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp ) - ) * ( 1.0 / 9.0 ); + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d0.x, d0.y, d0.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d1.x, d1.y, d1.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d2.x, d2.y, d2.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d3.x, d3.y, d3.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d4.x, d4.y, d4.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d5.x, d5.y, d5.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d6.x, d6.y, d6.x ), texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d7.x, d7.y, d7.x ), texelSize.y ), dp ) + ) * ( 1.0 / 8.0 ); #else // no percentage-closer filtering From fbacf2c66de5b7bd92d0074d23c16bbf54ab1b82 Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Wed, 5 Nov 2025 00:16:21 +0900 Subject: [PATCH 2/3] Updated interleavedGradientNoise implementation. --- .../shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js index 8aca993aacc6ae..3de73661a32a0e 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js @@ -65,10 +65,9 @@ export default /* glsl */` #endif - float interleavedGradientNoise( vec2 position_screen ) { + float interleavedGradientNoise( vec2 position ) { - vec3 magic = vec3( 0.06711056, 0.00583715, 52.9829189 ); - return fract( magic.z * fract( dot( position_screen, magic.xy ) ) ); + return fract( 52.9829189 * fract( dot( position, vec2( 0.06711056, 0.00583715 ) ) ) ); } From 7d8094806cfd9427760962f83db82b72f423d01d Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Wed, 5 Nov 2025 02:07:34 +0900 Subject: [PATCH 3/3] Clean up. --- .../shadowmap_pars_fragment.glsl.js | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js index 3de73661a32a0e..3b0df63bc48029 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js @@ -154,19 +154,23 @@ export default /* glsl */` float phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2; + vec2 offset = texelSize * shadowRadius; + vec2 uv = shadowCoord.xy; + float compare = shadowCoord.z; + shadow = ( - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 0, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 1, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 2, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 3, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 4, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 5, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 6, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 7, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 8, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 9, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 10, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + - texture2DCompare( shadowMap, shadowCoord.xy + vogelDiskSample( 11, 12, phi ) * texelSize * shadowRadius, shadowCoord.z ) + texture2DCompare( shadowMap, uv + vogelDiskSample( 0, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 1, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 2, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 3, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 4, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 5, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 6, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 7, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 8, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 9, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 10, 12, phi ) * offset, compare ) + + texture2DCompare( shadowMap, uv + vogelDiskSample( 11, 12, phi ) * offset, compare ) ) * ( 1.0 / 12.0 ); #elif defined( SHADOWMAP_TYPE_PCF_SOFT ) @@ -317,24 +321,27 @@ export default /* glsl */` float phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2; - vec2 d0 = vogelDiskSample( 0, 8, phi ) * shadowRadius * texelSize.y; - vec2 d1 = vogelDiskSample( 1, 8, phi ) * shadowRadius * texelSize.y; - vec2 d2 = vogelDiskSample( 2, 8, phi ) * shadowRadius * texelSize.y; - vec2 d3 = vogelDiskSample( 3, 8, phi ) * shadowRadius * texelSize.y; - vec2 d4 = vogelDiskSample( 4, 8, phi ) * shadowRadius * texelSize.y; - vec2 d5 = vogelDiskSample( 5, 8, phi ) * shadowRadius * texelSize.y; - vec2 d6 = vogelDiskSample( 6, 8, phi ) * shadowRadius * texelSize.y; - vec2 d7 = vogelDiskSample( 7, 8, phi ) * shadowRadius * texelSize.y; + float offset = shadowRadius * texelSize.y; + float texelSizeY = texelSize.y; + + vec2 d0 = vogelDiskSample( 0, 8, phi ) * offset; + vec2 d1 = vogelDiskSample( 1, 8, phi ) * offset; + vec2 d2 = vogelDiskSample( 2, 8, phi ) * offset; + vec2 d3 = vogelDiskSample( 3, 8, phi ) * offset; + vec2 d4 = vogelDiskSample( 4, 8, phi ) * offset; + vec2 d5 = vogelDiskSample( 5, 8, phi ) * offset; + vec2 d6 = vogelDiskSample( 6, 8, phi ) * offset; + vec2 d7 = vogelDiskSample( 7, 8, phi ) * offset; shadow = ( - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d0.x, d0.y, d0.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d1.x, d1.y, d1.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d2.x, d2.y, d2.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d3.x, d3.y, d3.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d4.x, d4.y, d4.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d5.x, d5.y, d5.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d6.x, d6.y, d6.x ), texelSize.y ), dp ) + - texture2DCompare( shadowMap, cubeToUV( bd3D + vec3( d7.x, d7.y, d7.x ), texelSize.y ), dp ) + texture2DCompare( shadowMap, cubeToUV( bd3D + d0.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d1.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d2.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d3.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d4.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d5.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d6.xyx, texelSizeY ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + d7.xyx, texelSizeY ), dp ) ) * ( 1.0 / 8.0 ); #else // no percentage-closer filtering