You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
7.5 KiB
256 lines
7.5 KiB
6 months ago
|
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 );
|