diff --git a/examples/files.json b/examples/files.json
index e70614edb858ee..bd549c1a22bf01 100644
--- a/examples/files.json
+++ b/examples/files.json
@@ -335,6 +335,7 @@
"webgpu_lights_custom",
"webgpu_lights_ies_spotlight",
"webgpu_lights_phong",
+ "webgpu_lights_physical",
"webgpu_lights_rectarealight",
"webgpu_lights_selective",
"webgpu_lights_tiled",
diff --git a/examples/screenshots/webgpu_lights_physical.jpg b/examples/screenshots/webgpu_lights_physical.jpg
new file mode 100644
index 00000000000000..b57683e1e8f67c
Binary files /dev/null and b/examples/screenshots/webgpu_lights_physical.jpg differ
diff --git a/examples/webgpu_lights_physical.html b/examples/webgpu_lights_physical.html
new file mode 100644
index 00000000000000..8fc7f91c762672
--- /dev/null
+++ b/examples/webgpu_lights_physical.html
@@ -0,0 +1,297 @@
+
+
+
+ three.js webgpu - physical lights
+
+
+
+
+
+
+
+
+
three.js - Physically accurate incandescent bulb by
Ben Houston
+ Real world scale: Brick cube is 50 cm in size. Globe is 50 cm in diameter.
+ Reinhard inline tonemapping with real-world light falloff (decay = 2).
+
+
+
+
+
+
+
diff --git a/src/materials/nodes/manager/NodeMaterialObserver.js b/src/materials/nodes/manager/NodeMaterialObserver.js
index 7a227cee0cb031..d7865a70fb6bf7 100644
--- a/src/materials/nodes/manager/NodeMaterialObserver.js
+++ b/src/materials/nodes/manager/NodeMaterialObserver.js
@@ -85,7 +85,7 @@ class NodeMaterialObserver {
if ( data === undefined ) {
- const { geometry, material } = renderObject;
+ const { geometry, material, object } = renderObject;
data = {
material: this.getMaterialData( material ),
@@ -94,18 +94,18 @@ class NodeMaterialObserver {
indexVersion: geometry.index ? geometry.index.version : null,
drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
},
- worldMatrix: renderObject.object.matrixWorld.clone()
+ worldMatrix: object.matrixWorld.clone()
};
- if ( renderObject.object.center ) {
+ if ( object.center ) {
- data.center = renderObject.object.center.clone();
+ data.center = object.center.clone();
}
- if ( renderObject.object.morphTargetInfluences ) {
+ if ( object.morphTargetInfluences ) {
- data.morphTargetInfluences = renderObject.object.morphTargetInfluences.slice();
+ data.morphTargetInfluences = object.morphTargetInfluences.slice();
}
@@ -289,7 +289,8 @@ class NodeMaterialObserver {
}
- // Compare each attribute
+ // compare each attribute
+
for ( const name of storedAttributeNames ) {
const storedAttributeData = storedAttributes[ name ];
@@ -297,7 +298,7 @@ class NodeMaterialObserver {
if ( attribute === undefined ) {
- // Attribute was removed
+ // attribute was removed
delete storedAttributes[ name ];
return false;
@@ -312,7 +313,8 @@ class NodeMaterialObserver {
}
- // Check index
+ // check index
+
const index = geometry.index;
const storedIndexVersion = storedGeometryData.indexVersion;
const currentIndexVersion = index ? index.version : null;
@@ -324,7 +326,8 @@ class NodeMaterialObserver {
}
- // Check drawRange
+ // check drawRange
+
if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {
storedGeometryData.drawRange.start = geometry.drawRange.start;
diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js
index 26ed9bf7179c7c..771d415350dcdd 100644
--- a/src/nodes/lighting/AnalyticLightNode.js
+++ b/src/nodes/lighting/AnalyticLightNode.js
@@ -47,6 +47,12 @@ class AnalyticLightNode extends LightingNode {
}
+ setupShadowNode() {
+
+ return shadow( this.light );
+
+ }
+
setupShadow( builder ) {
const { renderer } = builder;
@@ -67,7 +73,7 @@ class AnalyticLightNode extends LightingNode {
} else {
- shadowNode = shadow( this.light );
+ shadowNode = this.setupShadowNode( builder );
}
diff --git a/src/nodes/lighting/PointLightNode.js b/src/nodes/lighting/PointLightNode.js
index 1ed4c7bae6657c..edc4fc8eff8c1f 100644
--- a/src/nodes/lighting/PointLightNode.js
+++ b/src/nodes/lighting/PointLightNode.js
@@ -5,6 +5,7 @@ import { lightViewPosition } from '../accessors/Lights.js';
import { positionView } from '../accessors/Position.js';
import { Fn } from '../tsl/TSLBase.js';
import { renderGroup } from '../core/UniformGroupNode.js';
+import { pointShadow } from './PointShadowNode.js';
export const directPointLight = Fn( ( { color, lightViewPosition, cutoffDistance, decayExponent }, builder ) => {
@@ -61,7 +62,15 @@ class PointLightNode extends AnalyticLightNode {
}
- setup() {
+ setupShadowNode() {
+
+ return pointShadow( this.light );
+
+ }
+
+ setup( builder ) {
+
+ super.setup( builder );
directPointLight( {
color: this.colorNode,
diff --git a/src/nodes/lighting/PointShadowNode.js b/src/nodes/lighting/PointShadowNode.js
new file mode 100644
index 00000000000000..c33e688287df13
--- /dev/null
+++ b/src/nodes/lighting/PointShadowNode.js
@@ -0,0 +1,254 @@
+import ShadowNode from './ShadowNode.js';
+import { uniform } from '../core/UniformNode.js';
+import { float, vec2, If, Fn, nodeObject } from '../tsl/TSLBase.js';
+import { reference } from '../accessors/ReferenceNode.js';
+import { texture } from '../accessors/TextureNode.js';
+import { max, abs, sign } from '../math/MathNode.js';
+import { sub, div } from '../math/OperatorNode.js';
+import { renderGroup } from '../core/UniformGroupNode.js';
+import { Vector2 } from '../../math/Vector2.js';
+import { Vector4 } from '../../math/Vector4.js';
+import { Color } from '../../math/Color.js';
+import { BasicShadowMap } from '../../constants.js';
+
+const _clearColor = /*@__PURE__*/ new Color();
+
+// cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D
+// vector suitable for 2D texture mapping. This code uses the following layout for the
+// 2D texture:
+//
+// xzXZ
+// y Y
+//
+// Y - Positive y direction
+// y - Negative y direction
+// X - Positive x direction
+// x - Negative x direction
+// Z - Positive z direction
+// z - Negative z direction
+//
+// Source and test bed:
+// https://gist.github.com/tschw/da10c43c467ce8afd0c4
+
+export const cubeToUV = /*@__PURE__*/ Fn( ( [ pos, texelSizeY ] ) => {
+
+ const v = pos.toVar();
+
+ // Number of texels to avoid at the edge of each square
+
+ const absV = abs( v );
+
+ // Intersect unit cube
+
+ const scaleToCube = div( 1.0, max( absV.x, max( absV.y, absV.z ) ) );
+ absV.mulAssign( scaleToCube );
+
+ // Apply scale to avoid seams
+
+ // two texels less per square (one texel will do for NEAREST)
+ v.mulAssign( scaleToCube.mul( texelSizeY.mul( 2 ).oneMinus() ) );
+
+ // Unwrap
+
+ // space: -1 ... 1 range for each square
+ //
+ // #X## dim := ( 4 , 2 )
+ // # # center := ( 1 , 1 )
+
+ const planar = vec2( v.xy ).toVar();
+
+ const almostATexel = texelSizeY.mul( 1.5 );
+ const almostOne = almostATexel.oneMinus();
+
+ If( absV.z.greaterThanEqual( almostOne ), () => {
+
+ If( v.z.greaterThan( 0.0 ), () => {
+
+ planar.x.assign( sub( 4.0, v.x ) );
+
+ } );
+
+ } ).ElseIf( absV.x.greaterThanEqual( almostOne ), () => {
+
+ const signX = sign( v.x );
+ planar.x.assign( v.z.mul( signX ).add( signX.mul( 2.0 ) ) );
+
+ } ).ElseIf( absV.y.greaterThanEqual( almostOne ), () => {
+
+ const signY = sign( v.y );
+ planar.x.assign( v.x.add( signY.mul( 2.0 ) ).add( 2.0 ) );
+ planar.y.assign( v.z.mul( signY ).sub( 2.0 ) );
+
+ } );
+
+ // Transform to UV space
+
+ // scale := 0.5 / dim
+ // translate := ( center + 0.5 ) / dim
+ return vec2( 0.125, 0.25 ).mul( planar ).add( vec2( 0.375, 0.75 ) ).flipY();
+
+} ).setLayout( {
+ name: 'cubeToUV',
+ type: 'vec2',
+ inputs: [
+ { name: 'pos', type: 'vec3' },
+ { name: 'texelSizeY', type: 'float' }
+ ]
+} );
+
+export const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize } ) => {
+
+ return texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp );
+
+} );
+
+export const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize, shadow } ) => {
+
+ const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup );
+ const offset = vec2( - 1.0, 1.0 ).mul( radius ).mul( texelSize.y );
+
+ return texture( depthTexture, cubeToUV( bd3D.add( offset.xyy ), texelSize.y ) ).compare( dp )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyy ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xyx ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyx ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxy ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxy ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxx ), texelSize.y ) ).compare( dp ) )
+ .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxx ), texelSize.y ) ).compare( dp ) )
+ .mul( 1.0 / 9.0 );
+
+} );
+
+const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow } ) => {
+
+ // for point lights, the uniform @vShadowCoord is re-purposed to hold
+ // the vector from the light to the world-space position of the fragment.
+ const lightToPosition = shadowCoord.xyz.toVar();
+ const lightToPositionLength = lightToPosition.length();
+
+ const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near );
+ const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far );
+ const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
+ const mapSize = uniform( shadow.mapSize ).setGroup( renderGroup );
+
+ const result = float( 1.0 ).toVar();
+
+ If( lightToPositionLength.sub( cameraFarLocal ).lessThanEqual( 0.0 ).and( lightToPositionLength.sub( cameraNearLocal ).greaterThanEqual( 0.0 ) ), () => {
+
+ // dp = normalized distance from light to fragment position
+ const dp = lightToPositionLength.sub( cameraNearLocal ).div( cameraFarLocal.sub( cameraNearLocal ) ).toVar(); // need to clamp?
+ dp.addAssign( bias );
+
+ // bd3D = base direction 3D
+ const bd3D = lightToPosition.normalize();
+ const texelSize = vec2( 1.0 ).div( mapSize.mul( vec2( 4.0, 2.0 ) ) );
+
+ // percentage-closer filtering
+ result.assign( filterFn( { depthTexture, bd3D, dp, texelSize, shadow } ) );
+
+ } );
+
+ return result;
+
+} );
+
+const _viewport = /*@__PURE__*/ new Vector4();
+const _viewportSize = /*@__PURE__*/ new Vector2();
+const _shadowMapSize = /*@__PURE__*/ new Vector2();
+
+//
+
+class PointShadowNode extends ShadowNode {
+
+ static get type() {
+
+ return 'PointShadowNode';
+
+ }
+
+ constructor( light, shadow = null ) {
+
+ super( light, shadow );
+
+ }
+
+ getShadowFilterFn( type ) {
+
+ return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter;
+
+ }
+
+ setupShadowCoord( builder, shadowPosition ) {
+
+ return shadowPosition;
+
+ }
+
+ setupShadowFilter( builder, { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ) {
+
+ return pointShadowFilter( { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } );
+
+ }
+
+ renderShadow( frame ) {
+
+ const { shadow, shadowMap, light } = this;
+ const { renderer, scene } = frame;
+
+ const shadowFrameExtents = shadow.getFrameExtents();
+
+ _shadowMapSize.copy( shadow.mapSize );
+ _shadowMapSize.multiply( shadowFrameExtents );
+
+ shadowMap.setSize( _shadowMapSize.width, _shadowMapSize.height );
+
+ _viewportSize.copy( shadow.mapSize );
+
+ //
+
+ const previousAutoClear = renderer.autoClear;
+
+ const previousClearColor = renderer.getClearColor( _clearColor );
+ const previousClearAlpha = renderer.getClearAlpha();
+
+ renderer.autoClear = false;
+ renderer.setClearColor( shadow.clearColor, shadow.clearAlpha );
+ renderer.clear();
+
+ const viewportCount = shadow.getViewportCount();
+
+ for ( let vp = 0; vp < viewportCount; vp ++ ) {
+
+ const viewport = shadow.getViewport( vp );
+
+ const x = _viewportSize.x * viewport.x;
+ const y = _shadowMapSize.y - _viewportSize.y - ( _viewportSize.y * viewport.y );
+
+ _viewport.set(
+ x,
+ y,
+ _viewportSize.x * viewport.z,
+ _viewportSize.y * viewport.w
+ );
+
+ shadowMap.viewport.copy( _viewport );
+
+ shadow.updateMatrices( light, vp );
+
+ renderer.render( scene, shadow.camera );
+
+ }
+
+ //
+
+ renderer.autoClear = previousAutoClear;
+ renderer.setClearColor( previousClearColor, previousClearAlpha );
+
+ }
+
+}
+
+export default PointShadowNode;
+
+export const pointShadow = ( light, shadow ) => nodeObject( new PointShadowNode( light, shadow ) );
diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js
index 66ece8456677ed..e088b24cc4766e 100644
--- a/src/nodes/lighting/ShadowNode.js
+++ b/src/nodes/lighting/ShadowNode.js
@@ -16,16 +16,40 @@ import { screenCoordinate } from '../display/ScreenNode.js';
import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
import { renderGroup } from '../core/UniformGroupNode.js';
import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js';
+import { objectPosition } from '../accessors/Object3DNode.js';
-const shadowPosition = vec3().toVar( 'shadowPosition' );
+const shadowWorldPosition = /*@__PURE__*/ vec3().toVar( 'shadowWorldPosition' );
-const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => {
+const linearDistance = /*@__PURE__*/ Fn( ( [ position, cameraNear, cameraFar ] ) => {
+
+ let dist = positionWorld.sub( position ).length();
+ dist = dist.sub( cameraNear ).div( cameraFar.sub( cameraNear ) );
+ dist = dist.saturate(); // clamp to [ 0, 1 ]
+
+ return dist;
+
+} );
+
+const linearShadowDistance = ( light ) => {
+
+ const camera = light.shadow.camera;
+
+ const nearDistance = reference( 'near', 'float', camera ).setGroup( renderGroup );
+ const farDistance = reference( 'far', 'float', camera ).setGroup( renderGroup );
+
+ const referencePosition = objectPosition( light );
+
+ return linearDistance( referencePosition, nearDistance, farDistance );
+
+};
+
+export const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord } ) => {
return texture( depthTexture, shadowCoord.xy ).compare( shadowCoord.z );
} );
-const PCFShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
+export const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow } ) => {
const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
@@ -64,7 +88,7 @@ const PCFShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
} );
-const PCFSoftShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
+export const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow } ) => {
const depthCompare = ( uv, compare ) => texture( depthTexture, uv ).compare( compare );
@@ -122,7 +146,7 @@ const PCFSoftShadowMap = Fn( ( { depthTexture, shadowCoord, shadow } ) => {
// VSM
-const VSMShadowMapNode = Fn( ( { depthTexture, shadowCoord } ) => {
+export const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord } ) => {
const occlusion = float( 1 ).toVar();
@@ -144,7 +168,7 @@ const VSMShadowMapNode = Fn( ( { depthTexture, shadowCoord } ) => {
} );
-const VSMPassVertical = Fn( ( { samples, radius, size, shadowPass } ) => {
+const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass } ) => {
const mean = float( 0 ).toVar();
const squaredMean = float( 0 ).toVar();
@@ -170,7 +194,7 @@ const VSMPassVertical = Fn( ( { samples, radius, size, shadowPass } ) => {
} );
-const VSMPassHorizontal = Fn( ( { samples, radius, size, shadowPass } ) => {
+const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass } ) => {
const mean = float( 0 ).toVar();
const squaredMean = float( 0 ).toVar();
@@ -196,7 +220,7 @@ const VSMPassHorizontal = Fn( ( { samples, radius, size, shadowPass } ) => {
} );
-const _shadowFilterLib = [ BasicShadowMap, PCFShadowMap, PCFSoftShadowMap, VSMShadowMapNode ];
+const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter ];
//
@@ -233,22 +257,93 @@ class ShadowNode extends Node {
}
+ setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow } ) {
+
+ 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 shadowNode = filterFn( { depthTexture, shadowCoord, shadow } );
+
+ return frustumTest.select( shadowNode, float( 1 ) );
+
+ }
+
+ setupShadowCoord( builder, shadowPosition ) {
+
+ const { shadow } = this;
+ const { renderer } = builder;
+
+ const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
+
+ let shadowCoord = shadowPosition;
+ 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 = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
+ const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );
+
+ coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal );
+
+ }
+
+ shadowCoord = vec3(
+ shadowCoord.x,
+ shadowCoord.y.oneMinus(), // follow webgpu standards
+ coordZ.add( bias )
+ );
+
+ return shadowCoord;
+
+ }
+
+ getShadowFilterFn( type ) {
+
+ return _shadowFilterLib[ type ];
+
+ }
+
setupShadow( builder ) {
const { renderer } = builder;
+ const { light, shadow } = this;
+
+ const shadowMapType = renderer.shadowMap.type;
+
if ( _overrideMaterial === null ) {
+ const depthNode = light.isPointLight ? linearShadowDistance( light ) : null;
+
_overrideMaterial = new NodeMaterial();
_overrideMaterial.fragmentNode = vec4( 0, 0, 0, 1 );
+ _overrideMaterial.depthNode = depthNode;
_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;
@@ -286,55 +381,14 @@ class ShadowNode extends Node {
//
const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
- const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup );
const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
- let shadowCoord = uniform( shadow.matrix ).setGroup( renderGroup ).mul( shadowPosition.add( transformedNormalWorld.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 = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );
- const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );
-
- coordZ = viewZToLogarithmicDepth( w.negate(), 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 shadowPosition = uniform( shadow.matrix ).setGroup( renderGroup ).mul( shadowWorldPosition.add( transformedNormalWorld.mul( normalBias ) ) );
+ const shadowCoord = this.setupShadowCoord( builder, shadowPosition );
//
- const filterFn = shadow.filterNode || _shadowFilterLib[ renderer.shadowMap.type ] || null;
+ const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null;
if ( filterFn === null ) {
@@ -342,8 +396,11 @@ class ShadowNode extends Node {
}
+ const shadowDepthTexture = ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture;
+
+ const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow } );
+
const shadowColor = texture( shadowMap.texture, shadowCoord );
- const shadowNode = frustumTest.select( filterFn( { depthTexture: ( shadowMapType === VSMShadowMap ) ? this.vsmShadowMapHorizontal.texture : depthTexture, shadowCoord, shadow } ), float( 1 ) );
const shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar();
this.shadowMap = shadowMap;
@@ -359,7 +416,7 @@ class ShadowNode extends Node {
return Fn( ( { material } ) => {
- shadowPosition.assign( material.shadowPositionNode || positionWorld );
+ shadowWorldPosition.assign( material.shadowPositionNode || positionWorld );
let node = this._node;
@@ -387,6 +444,19 @@ class ShadowNode extends Node {
}
+ renderShadow( frame ) {
+
+ const { shadow, shadowMap, light } = this;
+ const { renderer, scene } = frame;
+
+ shadow.updateMatrices( light );
+
+ shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height );
+
+ renderer.render( scene, shadow.camera );
+
+ }
+
updateShadow( frame ) {
const { shadowMap, light, shadow } = this;
@@ -401,9 +471,6 @@ class ShadowNode extends Node {
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();
@@ -420,7 +487,8 @@ class ShadowNode extends Node {
} );
renderer.setRenderTarget( shadowMap );
- renderer.render( scene, shadow.camera );
+
+ this.renderShadow( frame );
renderer.setRenderObjectFunction( currentRenderObjectFunction );
diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js
index fb9fbe0e1050fe..50daed6be71425 100644
--- a/src/renderers/common/RenderObject.js
+++ b/src/renderers/common/RenderObject.js
@@ -379,6 +379,8 @@ export default class RenderObject {
}
+ cacheKey += object.receiveShadow + ',';
+
return hashString( cacheKey );
}