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 ); }