|
|
@@ -7,6 +7,8 @@ import { renderGroup } from '../core/UniformGroupNode.js';
|
|
|
import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
|
|
|
import { objectPosition } from '../accessors/Object3DNode.js';
|
|
|
import { positionWorld } from '../accessors/Position.js';
|
|
|
+import { screenCoordinate } from '../display/ScreenNode.js';
|
|
|
+import { interleavedGradientNoise, vogelDiskSample } from '../utils/PostProcessingUtils.js';
|
|
|
|
|
|
const shadowMaterialLib = /*@__PURE__*/ new WeakMap();
|
|
|
|
|
|
@@ -35,7 +37,11 @@ export const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord
|
|
|
} );
|
|
|
|
|
|
/**
|
|
|
- * A shadow filtering function performing PCF filtering.
|
|
|
+ * A shadow filtering function performing PCF filtering with Vogel disk sampling and IGN.
|
|
|
+ *
|
|
|
+ * Uses 5 samples distributed via Vogel disk pattern, rotated per-pixel using Interleaved
|
|
|
+ * Gradient Noise (IGN) to break up banding artifacts. Combined with hardware PCF (4-tap
|
|
|
+ * filtering per sample), this effectively provides 20 filtered taps with better distribution.
|
|
|
*
|
|
|
* @method
|
|
|
* @param {Object} inputs - The input parameter object.
|
|
|
@@ -64,34 +70,19 @@ export const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord,
|
|
|
const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
|
|
|
|
|
|
const texelSize = vec2( 1 ).div( mapSize );
|
|
|
- const dx0 = texelSize.x.negate().mul( radius );
|
|
|
- const dy0 = texelSize.y.negate().mul( radius );
|
|
|
- const dx1 = texelSize.x.mul( radius );
|
|
|
- const dy1 = texelSize.y.mul( radius );
|
|
|
- const dx2 = dx0.div( 2 );
|
|
|
- const dy2 = dy0.div( 2 );
|
|
|
- const dx3 = dx1.div( 2 );
|
|
|
- const dy3 = dy1.div( 2 );
|
|
|
+ const radiusScaled = radius.mul( texelSize.x );
|
|
|
+
|
|
|
+ // Use IGN to rotate sampling pattern per pixel (phi = IGN * 2π)
|
|
|
+ const phi = interleavedGradientNoise( screenCoordinate.xy ).mul( 6.28318530718 );
|
|
|
|
|
|
+ // 5 samples using Vogel disk distribution
|
|
|
return add(
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy, shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ),
|
|
|
- depthCompare( shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z )
|
|
|
- ).mul( 1 / 17 );
|
|
|
+ depthCompare( shadowCoord.xy.add( vogelDiskSample( 0, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
|
|
|
+ depthCompare( shadowCoord.xy.add( vogelDiskSample( 1, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
|
|
|
+ depthCompare( shadowCoord.xy.add( vogelDiskSample( 2, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
|
|
|
+ depthCompare( shadowCoord.xy.add( vogelDiskSample( 3, 5, phi ).mul( radiusScaled ) ), shadowCoord.z ),
|
|
|
+ depthCompare( shadowCoord.xy.add( vogelDiskSample( 4, 5, phi ).mul( radiusScaled ) ), shadowCoord.z )
|
|
|
+ ).mul( 1 / 5 );
|
|
|
|
|
|
} );
|
|
|
|