256 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			256 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import LightingNode from './LightingNode.js'; | ||
|  | import { NodeUpdateType } from '../core/constants.js'; | ||
|  | import { uniform } from '../core/UniformNode.js'; | ||
|  | import { addNodeClass } from '../core/Node.js'; | ||
|  | import { /*vec2,*/ vec3, vec4 } from '../shadernode/ShaderNode.js'; | ||
|  | import { reference } from '../accessors/ReferenceNode.js'; | ||
|  | import { texture } from '../accessors/TextureNode.js'; | ||
|  | import { positionWorld } from '../accessors/PositionNode.js'; | ||
|  | import { normalWorld } from '../accessors/NormalNode.js'; | ||
|  | import { WebGPUCoordinateSystem } from 'three'; | ||
|  | //import { add } from '../math/OperatorNode.js';
 | ||
|  | 
 | ||
|  | import { Color, DepthTexture, NearestFilter, LessCompare, NoToneMapping } from 'three'; | ||
|  | 
 | ||
|  | let overrideMaterial = null; | ||
|  | 
 | ||
|  | class AnalyticLightNode extends LightingNode { | ||
|  | 
 | ||
|  | 	constructor( light = null ) { | ||
|  | 
 | ||
|  | 		super(); | ||
|  | 
 | ||
|  | 		this.updateType = NodeUpdateType.FRAME; | ||
|  | 
 | ||
|  | 		this.light = light; | ||
|  | 
 | ||
|  | 		this.rtt = null; | ||
|  | 		this.shadowNode = null; | ||
|  | 		this.shadowMaskNode = null; | ||
|  | 
 | ||
|  | 		this.color = new Color(); | ||
|  | 		this._defaultColorNode = uniform( this.color ); | ||
|  | 
 | ||
|  | 		this.colorNode = this._defaultColorNode; | ||
|  | 
 | ||
|  | 		this.isAnalyticLightNode = true; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	getCacheKey() { | ||
|  | 
 | ||
|  | 		return super.getCacheKey() + '-' + ( this.light.id + '-' + ( this.light.castShadow ? '1' : '0' ) ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	getHash() { | ||
|  | 
 | ||
|  | 		return this.light.uuid; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	setupShadow( builder ) { | ||
|  | 
 | ||
|  | 		const { object } = builder; | ||
|  | 
 | ||
|  | 		if ( object.receiveShadow === false ) return; | ||
|  | 
 | ||
|  | 		let shadowNode = this.shadowNode; | ||
|  | 
 | ||
|  | 		if ( shadowNode === null ) { | ||
|  | 
 | ||
|  | 			if ( overrideMaterial === null ) { | ||
|  | 
 | ||
|  | 				overrideMaterial = builder.createNodeMaterial(); | ||
|  | 				overrideMaterial.fragmentNode = vec4( 0, 0, 0, 1 ); | ||
|  | 				overrideMaterial.isShadowNodeMaterial = true; // Use to avoid other overrideMaterial override material.fragmentNode unintentionally when using material.shadowNode
 | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			const shadow = this.light.shadow; | ||
|  | 			const rtt = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height ); | ||
|  | 
 | ||
|  | 			const depthTexture = new DepthTexture(); | ||
|  | 			depthTexture.minFilter = NearestFilter; | ||
|  | 			depthTexture.magFilter = NearestFilter; | ||
|  | 			depthTexture.image.width = shadow.mapSize.width; | ||
|  | 			depthTexture.image.height = shadow.mapSize.height; | ||
|  | 			depthTexture.compareFunction = LessCompare; | ||
|  | 
 | ||
|  | 			rtt.depthTexture = depthTexture; | ||
|  | 
 | ||
|  | 			shadow.camera.updateProjectionMatrix(); | ||
|  | 
 | ||
|  | 			//
 | ||
|  | 
 | ||
|  | 			const bias = reference( 'bias', 'float', shadow ); | ||
|  | 			const normalBias = reference( 'normalBias', 'float', shadow ); | ||
|  | 
 | ||
|  | 			const position = object.material.shadowPositionNode || positionWorld; | ||
|  | 
 | ||
|  | 			let shadowCoord = uniform( shadow.matrix ).mul( position.add( normalWorld.mul( normalBias ) ) ); | ||
|  | 			shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); | ||
|  | 
 | ||
|  | 			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 ) ); | ||
|  | 
 | ||
|  | 			let coordZ = shadowCoord.z.add( bias ); | ||
|  | 
 | ||
|  | 			if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) { | ||
|  | 
 | ||
|  | 				coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Convertion [ 0, 1 ] to [ - 1, 1 ]
 | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 			shadowCoord = vec3( | ||
|  | 				shadowCoord.x, | ||
|  | 				shadowCoord.y.oneMinus(), // follow webgpu standards
 | ||
|  | 				coordZ | ||
|  | 			); | ||
|  | 
 | ||
|  | 			const textureCompare = ( depthTexture, shadowCoord, compare ) => texture( depthTexture, shadowCoord ).compare( compare ); | ||
|  | 			//const textureCompare = ( depthTexture, shadowCoord, compare ) => compare.step( texture( depthTexture, shadowCoord ) );
 | ||
|  | 
 | ||
|  | 			// BasicShadowMap
 | ||
|  | 
 | ||
|  | 			shadowNode = textureCompare( depthTexture, shadowCoord.xy, shadowCoord.z ); | ||
|  | 
 | ||
|  | 			// PCFShadowMap
 | ||
|  | 			/* | ||
|  | 			const mapSize = reference( 'mapSize', 'vec2', shadow ); | ||
|  | 			const radius = reference( 'radius', 'float', shadow ); | ||
|  | 
 | ||
|  | 			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.mul( 2 ); | ||
|  | 			const dy2 = dy0.mul( 2 ); | ||
|  | 			const dx3 = dx1.mul( 2 ); | ||
|  | 			const dy3 = dy1.mul( 2 ); | ||
|  | 
 | ||
|  | 			shadowNode = add( | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy, shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ), | ||
|  | 				textureCompare( depthTexture, shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z ) | ||
|  | 			).mul( 1 / 17 ); | ||
|  | 			 */ | ||
|  | 			//
 | ||
|  | 
 | ||
|  | 			const shadowColor = texture( rtt.texture, shadowCoord ); | ||
|  | 			const shadowMaskNode = frustumTest.mix( 1, shadowNode.mix( shadowColor.a.mix( 1, shadowColor ), 1 ) ); | ||
|  | 
 | ||
|  | 			this.rtt = rtt; | ||
|  | 			this.colorNode = this.colorNode.mul( shadowMaskNode ); | ||
|  | 
 | ||
|  | 			this.shadowNode = shadowNode; | ||
|  | 			this.shadowMaskNode = shadowMaskNode; | ||
|  | 
 | ||
|  | 			//
 | ||
|  | 
 | ||
|  | 			this.updateBeforeType = NodeUpdateType.RENDER; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	setup( builder ) { | ||
|  | 
 | ||
|  | 		if ( this.light.castShadow ) this.setupShadow( builder ); | ||
|  | 		else if ( this.shadowNode !== null ) this.disposeShadow(); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateShadow( frame ) { | ||
|  | 
 | ||
|  | 		const { rtt, light } = this; | ||
|  | 		const { renderer, scene } = frame; | ||
|  | 
 | ||
|  | 		const currentOverrideMaterial = scene.overrideMaterial; | ||
|  | 
 | ||
|  | 		scene.overrideMaterial = overrideMaterial; | ||
|  | 
 | ||
|  | 		rtt.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height ); | ||
|  | 
 | ||
|  | 		light.shadow.updateMatrices( light ); | ||
|  | 
 | ||
|  | 		const currentToneMapping = renderer.toneMapping; | ||
|  | 		const currentRenderTarget = renderer.getRenderTarget(); | ||
|  | 		const currentRenderObjectFunction = renderer.getRenderObjectFunction(); | ||
|  | 
 | ||
|  | 		renderer.setRenderObjectFunction( ( object, ...params ) => { | ||
|  | 
 | ||
|  | 			if ( object.castShadow === true ) { | ||
|  | 
 | ||
|  | 				renderer.renderObject( object, ...params ); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} ); | ||
|  | 
 | ||
|  | 		renderer.setRenderTarget( rtt ); | ||
|  | 		renderer.toneMapping = NoToneMapping; | ||
|  | 
 | ||
|  | 		renderer.render( scene, light.shadow.camera ); | ||
|  | 
 | ||
|  | 		renderer.setRenderTarget( currentRenderTarget ); | ||
|  | 		renderer.setRenderObjectFunction( currentRenderObjectFunction ); | ||
|  | 
 | ||
|  | 		renderer.toneMapping = currentToneMapping; | ||
|  | 
 | ||
|  | 		scene.overrideMaterial = currentOverrideMaterial; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	disposeShadow() { | ||
|  | 
 | ||
|  | 		this.rtt.dispose(); | ||
|  | 
 | ||
|  | 		this.shadowNode = null; | ||
|  | 		this.shadowMaskNode = null; | ||
|  | 		this.rtt = null; | ||
|  | 
 | ||
|  | 		this.colorNode = this._defaultColorNode; | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	updateBefore( frame ) { | ||
|  | 
 | ||
|  | 		const { light } = this; | ||
|  | 
 | ||
|  | 		if ( light.castShadow ) this.updateShadow( frame ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	update( /*frame*/ ) { | ||
|  | 
 | ||
|  | 		const { light } = this; | ||
|  | 
 | ||
|  | 		this.color.copy( light.color ).multiplyScalar( light.intensity ); | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | export default AnalyticLightNode; | ||
|  | 
 | ||
|  | addNodeClass( 'AnalyticLightNode', AnalyticLightNode ); |