Просмотр исходного кода

WebGPURenderer: Introduce `ShadowNode` (#29567)

* introduce shadow node

* undo test setting

* lost in merging

* cleanup

---------

Co-authored-by: aardgoose <angus.sawyer@email.com>
aardgoose 1 год назад
Родитель
Сommit
b378fbac40
4 измененных файлов с 492 добавлено и 444 удалено
  1. 1 0
      src/nodes/Nodes.js
  2. 1 0
      src/nodes/TSL.js
  3. 6 444
      src/nodes/lighting/AnalyticLightNode.js
  4. 484 0
      src/nodes/lighting/ShadowNode.js

+ 1 - 0
src/nodes/Nodes.js

@@ -131,6 +131,7 @@ export { default as BasicEnvironmentNode } from './lighting/BasicEnvironmentNode
 export { default as IrradianceNode } from './lighting/IrradianceNode.js';
 export { default as AONode } from './lighting/AONode.js';
 export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js';
+export { default as ShadowNode } from './lighting/ShadowNode.js';
 
 // pmrem
 export { default as PMREMNode } from './pmrem/PMREMNode.js';

+ 1 - 0
src/nodes/TSL.js

@@ -130,6 +130,7 @@ export * from './gpgpu/AtomicFunctionNode.js';
 export * from './accessors/Lights.js';
 export * from './lighting/LightsNode.js';
 export * from './lighting/LightingContextNode.js';
+export * from './lighting/ShadowNode.js';
 
 // pmrem
 export * from './pmrem/PMREMNode.js';

+ 6 - 444
src/nodes/lighting/AnalyticLightNode.js

@@ -1,207 +1,10 @@
 import LightingNode from './LightingNode.js';
 import { NodeUpdateType } from '../core/constants.js';
 import { uniform } from '../core/UniformNode.js';
-import { float, vec2, vec3, vec4, If, int, Fn } from '../tsl/TSLBase.js';
-import { reference } from '../accessors/ReferenceNode.js';
-import { texture } from '../accessors/TextureNode.js';
-import { positionWorld } from '../accessors/Position.js';
-import { normalWorld } from '../accessors/Normal.js';
-import { mix, fract, step, max, clamp, sqrt } from '../math/MathNode.js';
-import { add, sub } from '../math/OperatorNode.js';
 import { Color } from '../../math/Color.js';
-import { DepthTexture } from '../../textures/DepthTexture.js';
-import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
-import QuadMesh from '../../renderers/common/QuadMesh.js';
-import { Loop } from '../utils/LoopNode.js';
-import { screenCoordinate } from '../display/ScreenNode.js';
-import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
 import { renderGroup } from '../core/UniformGroupNode.js';
-import { perspectiveDepthToLogarithmicDepth } from '../display/ViewportDepthNode.js';
 import { hash } from '../core/NodeUtils.js';
-
-const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => {
-
-	return texture( depthTexture, shadowCoord.xy ).compare( shadowCoord.z );
-
-} );
-
-const PCFShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
-
-	const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
-
-	const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
-	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 );
-
-	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 );
-
-} );
-
-const PCFSoftShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
-
-	const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
-
-	const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
-
-	const texelSize = vec2( 1 ).div( mapSize );
-	const dx = texelSize.x;
-	const dy = texelSize.y;
-
-	const uv = shadowCoord.xy;
-	const f = fract( uv.mul( mapSize ).add( 0.5 ) );
-	uv.subAssign( f.mul( texelSize ) );
-
-	return add(
-		depthCompare( uv, shadowCoord.z ),
-		depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ),
-		depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ),
-		depthCompare( uv.add( texelSize ), shadowCoord.z ),
-		mix(
-			depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ),
-			depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ),
-			f.x
-		),
-		mix(
-			depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ),
-			depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ),
-			f.x
-		),
-		mix(
-			depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ),
-			depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ),
-			f.y
-		),
-		mix(
-			depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ),
-			depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ),
-			f.y
-		),
-		mix(
-			mix(
-				depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ),
-				depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ),
-				f.x
-			),
-			mix(
-				depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ),
-				depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ),
-				f.x
-			),
-			f.y
-		)
-	).mul( 1 / 9 );
-
-} );
-
-// VSM
-
-const VSMShadowMapNode = Fn( ( { depthTexture, shadowCoord } ) => {
-
-	const occlusion = float( 1 ).toVar();
-
-	const distribution = texture( depthTexture ).uv( shadowCoord.xy ).rg;
-
-	const hardShadow = step( shadowCoord.z, distribution.x );
-
-	If( hardShadow.notEqual( float( 1.0 ) ), () => {
-
-		const distance = shadowCoord.z.sub( distribution.x );
-		const variance = max( 0, distribution.y.mul( distribution.y ) );
-		let softnessProbability = variance.div( variance.add( distance.mul( distance ) ) ); // Chebeyshevs inequality
-		softnessProbability = clamp( sub( softnessProbability, 0.3 ).div( 0.95 - 0.3 ) );
-		occlusion.assign( clamp( max( hardShadow, softnessProbability ) ) );
-
-	} );
-
-	return occlusion;
-
-} );
-
-const VSMPassVertical = Fn( ( { samples, radius, size, shadowPass } ) => {
-
-	const mean = float( 0 ).toVar();
-	const squaredMean = float( 0 ).toVar();
-
-	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
-	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
-
-	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
-
-		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
-
-		const depth = shadowPass.uv( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) ).x;
-		mean.addAssign( depth );
-		squaredMean.addAssign( depth.mul( depth ) );
-
-	} );
-
-	mean.divAssign( samples );
-	squaredMean.divAssign( samples );
-
-	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
-	return vec2( mean, std_dev );
-
-} );
-
-const VSMPassHorizontal = Fn( ( { samples, radius, size, shadowPass } ) => {
-
-	const mean = float( 0 ).toVar();
-	const squaredMean = float( 0 ).toVar();
-
-	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
-	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
-
-	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
-
-		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
-
-		const distribution = shadowPass.uv( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) );
-		mean.addAssign( distribution.x );
-		squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) );
-
-	} );
-
-	mean.divAssign( samples );
-	squaredMean.divAssign( samples );
-
-	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
-	return vec2( mean, std_dev );
-
-} );
-
-const _shadowFilterLib = [ BasicShadowMap, PCFShadowMap, PCFSoftShadowMap, VSMShadowMapNode ];
-
-//
-
-let _overrideMaterial = null;
-const _quadMesh = /*@__PURE__*/ new QuadMesh();
+import { shadow } from './ShadowNode.js';
 
 class AnalyticLightNode extends LightingNode {
 
@@ -224,16 +27,9 @@ class AnalyticLightNode extends LightingNode {
 
 		this.baseColorNode = null;
 
-		this.shadowMap = null;
 		this.shadowNode = null;
 		this.shadowColorNode = null;
 
-		this.vsmShadowMapVertical = null;
-		this.vsmShadowMapHorizontal = null;
-
-		this.vsmMaterialVertical = null;
-		this.vsmMaterialHorizontal = null;
-
 		this.isAnalyticLightNode = true;
 
 	}
@@ -252,7 +48,7 @@ class AnalyticLightNode extends LightingNode {
 
 	setupShadow( builder ) {
 
-		const { object, renderer } = builder;
+		const { renderer } = builder;
 
 		if ( renderer.shadowMap.enabled === false ) return;
 
@@ -260,121 +56,11 @@ class AnalyticLightNode extends LightingNode {
 
 		if ( shadowColorNode === null ) {
 
-			if ( _overrideMaterial === null ) {
-
-				_overrideMaterial = new NodeMaterial();
-				_overrideMaterial.fragmentNode = vec4( 0, 0, 0, 1 );
-				_overrideMaterial.isShadowNodeMaterial = true; // Use to avoid other overrideMaterial override material.fragmentNode unintentionally when using material.shadowNode
-				_overrideMaterial.name = 'ShadowMaterial';
-
-			}
-
-			const shadowMapType = renderer.shadowMap.type;
-			const shadow = this.light.shadow;
-
-			const depthTexture = new DepthTexture();
-			depthTexture.compareFunction = LessCompare;
-
-			const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
-			shadowMap.depthTexture = depthTexture;
-
-			shadow.camera.updateProjectionMatrix();
-
-			// VSM
-
-			if ( shadowMapType === VSMShadowMap ) {
-
-				depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare()
-
-				this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
-				this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
-
-				const shadowPassVertical = texture( depthTexture );
-				const shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture );
-
-				const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup );
-				const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
-				const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
-
-				let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() );
-				material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical } ).context( builder.getSharedContext() );
-				material.name = 'VSMVertical';
-
-				material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() );
-				material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal } ).context( builder.getSharedContext() );
-				material.name = 'VSMHorizontal';
-
-			}
-
-			//
-
-			const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
-			const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
-			const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
-
-			const position = object.material.shadowPositionNode || positionWorld;
-
-			let shadowCoord = uniform( shadow.matrix ).setGroup( renderGroup ).mul( position.add( normalWorld.mul( normalBias ) ) );
-
-			let coordZ;
-
-			if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) {
-
-				shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
-
-				coordZ = shadowCoord.z;
-
-				if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) {
-
-					coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ]
-
-				}
-
-			} else {
-
-				const w = shadowCoord.w;
-				shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z
-
-				// The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
-				// updated to use the shadow camera. So, we have to declare our own "local" ones here.
-				// TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
-				const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near );
-				const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far );
-
-				coordZ = perspectiveDepthToLogarithmicDepth( w, cameraNearLocal, cameraFarLocal );
-
-			}
-
-			shadowCoord = vec3(
-				shadowCoord.x,
-				shadowCoord.y.oneMinus(), // follow webgpu standards
-				coordZ.add( bias )
-			);
-
-			const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
-				.and( shadowCoord.x.lessThanEqual( 1 ) )
-				.and( shadowCoord.y.greaterThanEqual( 0 ) )
-				.and( shadowCoord.y.lessThanEqual( 1 ) )
-				.and( shadowCoord.z.lessThanEqual( 1 ) );
-
-			//
-
-			const filterFn = shadow.filterNode || _shadowFilterLib[ renderer.shadowMap.type ] || null;
-
-			if ( filterFn === null ) {
-
-				throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' );
-
-			}
-
-			const shadowColor = texture( shadowMap.texture, shadowCoord );
-			const shadowNode = frustumTest.select( filterFn( { depthTexture: ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture, shadowCoord, shadow } ), float( 1 ) );
-
-			this.shadowMap = shadowMap;
-			this.light.shadow.map = shadowMap;
+			const shadowNode = shadow( this.light );
 
 			this.shadowNode = shadowNode;
-			this.shadowColorNode = shadowColorNode = this.colorNode.mul( mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ) );
+
+			this.shadowColorNode = shadowColorNode = this.colorNode.mul( shadowNode );
 
 			this.baseColorNode = this.colorNode;
 
@@ -384,8 +70,6 @@ class AnalyticLightNode extends LightingNode {
 
 		this.colorNode = shadowColorNode;
 
-		this.updateBeforeType = NodeUpdateType.RENDER;
-
 	}
 
 	setup( builder ) {
@@ -402,129 +86,7 @@ class AnalyticLightNode extends LightingNode {
 
 		} else if ( this.shadowNode !== null ) {
 
-			this.disposeShadow();
-
-		}
-
-	}
-
-	updateShadow( frame ) {
-
-		const { shadowMap, light } = this;
-		const { renderer, scene, camera } = frame;
-
-		const shadowType = renderer.shadowMap.type;
-
-		const depthVersion = shadowMap.depthTexture.version;
-		this._depthVersionCached = depthVersion;
-
-		const currentOverrideMaterial = scene.overrideMaterial;
-
-		scene.overrideMaterial = _overrideMaterial;
-
-		shadowMap.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height );
-
-		light.shadow.updateMatrices( light );
-		light.shadow.camera.layers.mask = camera.layers.mask;
-
-		const currentRenderTarget = renderer.getRenderTarget();
-		const currentRenderObjectFunction = renderer.getRenderObjectFunction();
-
-		renderer.setRenderObjectFunction( ( object, ...params ) => {
-
-			if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) {
-
-				renderer.renderObject( object, ...params );
-
-			}
-
-		} );
-
-		renderer.setRenderTarget( shadowMap );
-		renderer.render( scene, light.shadow.camera );
-
-		renderer.setRenderObjectFunction( currentRenderObjectFunction );
-
-		// vsm blur pass
-
-		if ( light.isPointLight !== true && shadowType === VSMShadowMap ) {
-
-			this.vsmPass( frame, light );
-
-		}
-
-		renderer.setRenderTarget( currentRenderTarget );
-
-		scene.overrideMaterial = currentOverrideMaterial;
-
-	}
-
-	vsmPass( frame, light ) {
-
-		const { renderer } = frame;
-
-		this.vsmShadowMapVertical.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height );
-		this.vsmShadowMapHorizontal.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height );
-
-		renderer.setRenderTarget( this.vsmShadowMapVertical );
-		_quadMesh.material = this.vsmMaterialVertical;
-		_quadMesh.render( renderer );
-
-		renderer.setRenderTarget( this.vsmShadowMapHorizontal );
-		_quadMesh.material = this.vsmMaterialHorizontal;
-		_quadMesh.render( renderer );
-
-	}
-
-	disposeShadow() {
-
-		this.shadowMap.dispose();
-		this.shadowMap = null;
-
-		if ( this.vsmShadowMapVertical !== null ) {
-
-			this.vsmShadowMapVertical.dispose();
-			this.vsmShadowMapVertical = null;
-
-			this.vsmMaterialVertical.dispose();
-			this.vsmMaterialVertical = null;
-
-		}
-
-		if ( this.vsmShadowMapHorizontal !== null ) {
-
-			this.vsmShadowMapHorizontal.dispose();
-			this.vsmShadowMapHorizontal = null;
-
-			this.vsmMaterialHorizontal.dispose();
-			this.vsmMaterialHorizontal = null;
-
-		}
-
-		this.shadowNode = null;
-		this.shadowColorNode = null;
-
-		this.baseColorNode = null;
-
-		this.updateBeforeType = NodeUpdateType.NONE;
-
-	}
-
-	updateBefore( frame ) {
-
-		const shadow = this.light.shadow;
-
-		const needsUpdate = shadow.needsUpdate || shadow.autoUpdate;
-
-		if ( needsUpdate ) {
-
-			this.updateShadow( frame );
-
-			if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) {
-
-				shadow.needsUpdate = false;
-
-			}
+			this.shadowNode.dispose();
 
 		}
 

+ 484 - 0
src/nodes/lighting/ShadowNode.js

@@ -0,0 +1,484 @@
+import Node from '../core/Node.js';
+import { NodeUpdateType } from '../core/constants.js';
+import { uniform } from '../core/UniformNode.js';
+import { float, vec2, vec3, vec4, If, int, Fn, nodeObject } from '../tsl/TSLBase.js';
+import { reference } from '../accessors/ReferenceNode.js';
+import { texture } from '../accessors/TextureNode.js';
+import { positionWorld } from '../accessors/Position.js';
+import { normalWorld } from '../accessors/Normal.js';
+import { mix, fract, step, max, clamp, sqrt } from '../math/MathNode.js';
+import { add, sub } from '../math/OperatorNode.js';
+import { DepthTexture } from '../../textures/DepthTexture.js';
+import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
+import QuadMesh from '../../renderers/common/QuadMesh.js';
+import { Loop } from '../utils/LoopNode.js';
+import { screenCoordinate } from '../display/ScreenNode.js';
+import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
+import { renderGroup } from '../core/UniformGroupNode.js';
+import { perspectiveDepthToLogarithmicDepth } from '../display/ViewportDepthNode.js';
+
+const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => {
+
+	return texture( depthTexture, shadowCoord.xy ).compare( shadowCoord.z );
+
+} );
+
+const PCFShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
+
+	const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
+
+	const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
+	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 );
+
+	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 );
+
+} );
+
+const PCFSoftShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
+
+	const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
+
+	const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
+
+	const texelSize = vec2( 1 ).div( mapSize );
+	const dx = texelSize.x;
+	const dy = texelSize.y;
+
+	const uv = shadowCoord.xy;
+	const f = fract( uv.mul( mapSize ).add( 0.5 ) );
+	uv.subAssign( f.mul( texelSize ) );
+
+	return add(
+		depthCompare( uv, shadowCoord.z ),
+		depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ),
+		depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ),
+		depthCompare( uv.add( texelSize ), shadowCoord.z ),
+		mix(
+			depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ),
+			depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ),
+			f.x
+		),
+		mix(
+			depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ),
+			depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ),
+			f.x
+		),
+		mix(
+			depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ),
+			depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ),
+			f.y
+		),
+		mix(
+			depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ),
+			depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ),
+			f.y
+		),
+		mix(
+			mix(
+				depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ),
+				depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ),
+				f.x
+			),
+			mix(
+				depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ),
+				depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ),
+				f.x
+			),
+			f.y
+		)
+	).mul( 1 / 9 );
+
+} );
+
+// VSM
+
+const VSMShadowMapNode = Fn( ( { depthTexture, shadowCoord } ) => {
+
+	const occlusion = float( 1 ).toVar();
+
+	const distribution = texture( depthTexture ).uv( shadowCoord.xy ).rg;
+
+	const hardShadow = step( shadowCoord.z, distribution.x );
+
+	If( hardShadow.notEqual( float( 1.0 ) ), () => {
+
+		const distance = shadowCoord.z.sub( distribution.x );
+		const variance = max( 0, distribution.y.mul( distribution.y ) );
+		let softnessProbability = variance.div( variance.add( distance.mul( distance ) ) ); // Chebeyshevs inequality
+		softnessProbability = clamp( sub( softnessProbability, 0.3 ).div( 0.95 - 0.3 ) );
+		occlusion.assign( clamp( max( hardShadow, softnessProbability ) ) );
+
+	} );
+
+	return occlusion;
+
+} );
+
+const VSMPassVertical = Fn( ( { samples, radius, size, shadowPass } ) => {
+
+	const mean = float( 0 ).toVar();
+	const squaredMean = float( 0 ).toVar();
+
+	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
+	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
+
+	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
+
+		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
+
+		const depth = shadowPass.uv( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) ).x;
+		mean.addAssign( depth );
+		squaredMean.addAssign( depth.mul( depth ) );
+
+	} );
+
+	mean.divAssign( samples );
+	squaredMean.divAssign( samples );
+
+	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
+	return vec2( mean, std_dev );
+
+} );
+
+const VSMPassHorizontal = Fn( ( { samples, radius, size, shadowPass } ) => {
+
+	const mean = float( 0 ).toVar();
+	const squaredMean = float( 0 ).toVar();
+
+	const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) );
+	const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( - 1 ) );
+
+	Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => {
+
+		const uvOffset = uvStart.add( float( i ).mul( uvStride ) );
+
+		const distribution = shadowPass.uv( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) );
+		mean.addAssign( distribution.x );
+		squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) );
+
+	} );
+
+	mean.divAssign( samples );
+	squaredMean.divAssign( samples );
+
+	const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) );
+	return vec2( mean, std_dev );
+
+} );
+
+const _shadowFilterLib = [ BasicShadowMap, PCFShadowMap, PCFSoftShadowMap, VSMShadowMapNode ];
+
+//
+
+let _overrideMaterial = null;
+const _quadMesh = /*@__PURE__*/ new QuadMesh();
+
+class ShadowNode extends Node {
+
+	static get type() {
+
+		return 'ShadowNode';
+
+	}
+
+	constructor( light, shadow = null ) {
+
+		super();
+
+		this.light = light;
+		this.shadow = shadow || light.shadow;
+
+		this.shadowMap = null;
+
+		this.vsmShadowMapVertical = null;
+		this.vsmShadowMapHorizontal = null;
+
+		this.vsmMaterialVertical = null;
+		this.vsmMaterialHorizontal = null;
+
+		this.updateBeforeType = NodeUpdateType.RENDER;
+		this._node = null;
+
+		this.isShadowNode = true;
+
+	}
+
+	setupShadow( builder ) {
+
+		const { object, renderer } = builder;
+
+		if ( _overrideMaterial === null ) {
+
+			_overrideMaterial = new NodeMaterial();
+			_overrideMaterial.fragmentNode = vec4( 0, 0, 0, 1 );
+			_overrideMaterial.isShadowNodeMaterial = true; // Use to avoid other overrideMaterial override material.fragmentNode unintentionally when using material.shadowNode
+			_overrideMaterial.name = 'ShadowMaterial';
+
+		}
+
+		const shadow = this.shadow;
+		const shadowMapType = renderer.shadowMap.type;
+
+		const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height );
+		depthTexture.compareFunction = LessCompare;
+
+		const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
+		shadowMap.depthTexture = depthTexture;
+
+		shadow.camera.updateProjectionMatrix();
+
+		// VSM
+
+		if ( shadowMapType === VSMShadowMap ) {
+
+			depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare()
+
+			this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
+			this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType } );
+
+			const shadowPassVertical = texture( depthTexture );
+			const shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture );
+
+			const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup );
+			const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
+			const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup );
+
+			let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() );
+			material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical } ).context( builder.getSharedContext() );
+			material.name = 'VSMVertical';
+
+			material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() );
+			material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal } ).context( builder.getSharedContext() );
+			material.name = 'VSMHorizontal';
+
+		}
+
+		//
+
+		const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
+		const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
+		const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
+
+		const position = object.material.shadowPositionNode || positionWorld;
+
+		let shadowCoord = uniform( shadow.matrix ).setGroup( renderGroup ).mul( position.add( normalWorld.mul( normalBias ) ) );
+
+		let coordZ;
+
+		if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) {
+
+			shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
+
+			coordZ = shadowCoord.z;
+
+			if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) {
+
+				coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ]
+
+			}
+
+		} else {
+
+			const w = shadowCoord.w;
+			shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z
+
+			// The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
+			// updated to use the shadow camera. So, we have to declare our own "local" ones here.
+			// TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
+			const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near );
+			const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far );
+
+			coordZ = perspectiveDepthToLogarithmicDepth( w, cameraNearLocal, cameraFarLocal );
+
+		}
+
+		shadowCoord = vec3(
+			shadowCoord.x,
+			shadowCoord.y.oneMinus(), // follow webgpu standards
+			coordZ.add( bias )
+		);
+
+		const frustumTest = shadowCoord.x.greaterThanEqual( 0 )
+			.and( shadowCoord.x.lessThanEqual( 1 ) )
+			.and( shadowCoord.y.greaterThanEqual( 0 ) )
+			.and( shadowCoord.y.lessThanEqual( 1 ) )
+			.and( shadowCoord.z.lessThanEqual( 1 ) );
+
+		//
+
+		const filterFn = shadow.filterNode || _shadowFilterLib[ renderer.shadowMap.type ] || null;
+
+		if ( filterFn === null ) {
+
+			throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' );
+
+		}
+
+		const shadowColor = texture( shadowMap.texture, shadowCoord );
+		const shadowNode = frustumTest.select( filterFn( { depthTexture: ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture, shadowCoord, shadow } ), float( 1 ) );
+
+		this.shadowMap = shadowMap;
+		this.shadow.map = shadowMap;
+
+		return mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) );
+
+	}
+
+	setup( builder ) {
+
+		if ( builder.renderer.shadowMap.enabled === false ) return;
+
+		return this._node !== null ? this._node : ( this._node = this.setupShadow( builder ) );
+
+	}
+
+	updateShadow( frame ) {
+
+		const { shadowMap, light, shadow } = this;
+		const { renderer, scene, camera } = frame;
+
+		const shadowType = renderer.shadowMap.type;
+
+		const depthVersion = shadowMap.depthTexture.version;
+		this._depthVersionCached = depthVersion;
+
+		const currentOverrideMaterial = scene.overrideMaterial;
+
+		scene.overrideMaterial = _overrideMaterial;
+
+		shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height );
+
+		shadow.updateMatrices( light );
+		shadow.camera.layers.mask = camera.layers.mask;
+
+		const currentRenderTarget = renderer.getRenderTarget();
+		const currentRenderObjectFunction = renderer.getRenderObjectFunction();
+
+		renderer.setRenderObjectFunction( ( object, ...params ) => {
+
+			if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) {
+
+				renderer.renderObject( object, ...params );
+
+			}
+
+		} );
+
+		renderer.setRenderTarget( shadowMap );
+		renderer.render( scene, shadow.camera );
+
+		renderer.setRenderObjectFunction( currentRenderObjectFunction );
+
+		// vsm blur pass
+
+		if ( light.isPointLight !== true && shadowType === VSMShadowMap ) {
+
+			this.vsmPass( renderer );
+
+		}
+
+		renderer.setRenderTarget( currentRenderTarget );
+
+		scene.overrideMaterial = currentOverrideMaterial;
+
+	}
+
+	vsmPass( renderer ) {
+
+		const { shadow } = this;
+
+		this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height );
+		this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height );
+
+		renderer.setRenderTarget( this.vsmShadowMapVertical );
+		_quadMesh.material = this.vsmMaterialVertical;
+		_quadMesh.render( renderer );
+
+		renderer.setRenderTarget( this.vsmShadowMapHorizontal );
+		_quadMesh.material = this.vsmMaterialHorizontal;
+		_quadMesh.render( renderer );
+
+	}
+
+	dispose() {
+
+		this.shadowMap.dispose();
+		this.shadowMap = null;
+
+		if ( this.vsmShadowMapVertical !== null ) {
+
+			this.vsmShadowMapVertical.dispose();
+			this.vsmShadowMapVertical = null;
+
+			this.vsmMaterialVertical.dispose();
+			this.vsmMaterialVertical = null;
+
+		}
+
+		if ( this.vsmShadowMapHorizontal !== null ) {
+
+			this.vsmShadowMapHorizontal.dispose();
+			this.vsmShadowMapHorizontal = null;
+
+			this.vsmMaterialHorizontal.dispose();
+			this.vsmMaterialHorizontal = null;
+
+		}
+
+		this.updateBeforeType = NodeUpdateType.NONE;
+
+	}
+
+	updateBefore( frame ) {
+
+		const { shadow } = this;
+
+		const needsUpdate = shadow.needsUpdate || shadow.autoUpdate;
+
+		if ( needsUpdate ) {
+
+			this.updateShadow( frame );
+
+			if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) {
+
+				shadow.needsUpdate = false;
+
+			}
+
+		}
+
+	}
+
+}
+
+export default ShadowNode;
+
+export const shadow = ( light, shadow ) => nodeObject( new ShadowNode( light, shadow ) );

粤ICP备19079148号