diff --git a/examples/jsm/tsl/WebGLNodesHandler.js b/examples/jsm/tsl/WebGLNodesHandler.js index 57e653979f5c5e..70ccdd04024857 100644 --- a/examples/jsm/tsl/WebGLNodesHandler.js +++ b/examples/jsm/tsl/WebGLNodesHandler.js @@ -87,15 +87,6 @@ class WebGLNodeBuilder extends GLSLNodeBuilder { } -const _lights = new Set(); -let _camera = null; - -function collectLight( child ) { - - if ( child.isLight && child.layers.test( _camera.layers ) ) _lights.add( child ); - -} - // produce and update reusable nodes for a scene class SceneContext { @@ -104,7 +95,6 @@ class SceneContext { // TODO: can / should we update the fog and environment node every frame for recompile? this.renderer = renderer; this.scene = scene; - this.sceneLights = []; this.lightsNode = renderer.lighting.getNode( scene ); this.fogNode = null; this.environmentNode = null; @@ -123,25 +113,9 @@ class SceneContext { } - update( object, camera ) { - - const { scene, lightsNode, sceneLights } = this; - - // update lighting - _camera = camera; - _lights.clear(); - - scene.traverseVisible( collectLight ); + update() { - // compile() can receive an object that has not been added to the target scene yet. - if ( object !== scene ) object.traverseVisible( collectLight ); - - _camera = null; - - sceneLights.length = 0; - sceneLights.push( ..._lights ); - - lightsNode.setLights( sceneLights ); + const { scene } = this; // update fog if ( this.prevFog !== scene.fog ) { @@ -413,17 +387,6 @@ export class WebGLNodesHandler { } - setupNodeMaterial( material ) { - - if ( material && material.isNodeMaterial ) { - - material.customProgramCacheKey = this.customProgramCacheKeyCallback; - material.onBeforeRender = this.onBeforeRenderCallback; - - } - - } - renderStart( scene, camera, targetScene = scene ) { const { nodeFrame, renderStack, renderer, sceneContexts } = this; @@ -440,40 +403,25 @@ export class WebGLNodesHandler { } - sceneContext.update( scene, camera ); + renderer.lighting.beginRender( targetScene ); + sceneContext.update(); renderStack.push( { sceneContext, camera } ); - // ensure all node material callbacks are initialized before - // traversal and build - scene.traverse( object => { - - const material = object.material; - - if ( material === undefined ) return; - - if ( Array.isArray( material ) ) { - - for ( let i = 0; i < material.length; i ++ ) { - - this.setupNodeMaterial( material[ i ] ); - - } - - } else { - - this.setupNodeMaterial( material ); + } - } + updateLights( lights ) { - } ); + const frame = this.renderStack[ this.renderStack.length - 1 ]; + frame.sceneContext.lightsNode.setLights( lights ); } renderEnd() { - const { nodeFrame, renderStack } = this; + const { nodeFrame, renderer, renderStack } = this; - renderStack.pop(); + const { sceneContext } = renderStack.pop(); + renderer.lighting.finishRender( sceneContext.scene ); const frame = renderStack[ renderStack.length - 1 ]; if ( frame ) { @@ -486,9 +434,11 @@ export class WebGLNodesHandler { } - setObject( object ) { + setObject( object, material ) { this.nodeFrame.object = object; + material.customProgramCacheKey = this.customProgramCacheKeyCallback; + material.onBeforeRender = this.onBeforeRenderCallback; } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 2f96342d87ad25..965959aa659483 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1353,7 +1353,7 @@ class WebGLRenderer { function prepareMaterial( material, scene, object ) { - if ( _nodesHandler !== null && material.isNodeMaterial ) _nodesHandler.setObject( object ); + if ( _nodesHandler !== null && material.isNodeMaterial ) _nodesHandler.setObject( object, material ); if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { @@ -1436,6 +1436,7 @@ class WebGLRenderer { } currentRenderState.setupLights(); + if ( _nodesHandler !== null ) _nodesHandler.updateLights( currentRenderState.state.lightsArray ); // node materials reference the shadow map when they are built, so it must exist by now @@ -1697,6 +1698,7 @@ class WebGLRenderer { projectObject( scene, camera, 0, _this.sortObjects ); currentRenderList.finish(); + if ( _nodesHandler !== null ) _nodesHandler.updateLights( currentRenderState.state.lightsArray ); if ( _this.sortObjects === true ) { @@ -2141,6 +2143,7 @@ class WebGLRenderer { function renderObject( object, scene, camera, geometry, material, group ) { + if ( _nodesHandler !== null && material.isNodeMaterial ) _nodesHandler.setObject( object, material ); object.onBeforeRender( _this, scene, camera, geometry, material, group ); object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index dde0337e0ac2a0..e07f8dc998d7a2 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -988,7 +988,6 @@ class WGSLNodeBuilder extends NodeBuilder { * @param {string} gatherSnippet - A WGSL snippet that represents the index of the channel to read. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL. * @return {string} The WGSL snippet. */ generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet ) { @@ -1026,7 +1025,6 @@ class WGSLNodeBuilder extends NodeBuilder { * @param {string} compareSnippet - A WGSL snippet that represents the reference value. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL. * @return {string} The WGSL snippet. */ generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet ) { @@ -1062,7 +1060,6 @@ class WGSLNodeBuilder extends NodeBuilder { * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture. - * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. * @return {string} The WGSL snippet. */ generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet ) { diff --git a/test/unit/addons/tsl/WebGLNodesHandler.tests.js b/test/unit/addons/tsl/WebGLNodesHandler.tests.js new file mode 100644 index 00000000000000..49912df375280a --- /dev/null +++ b/test/unit/addons/tsl/WebGLNodesHandler.tests.js @@ -0,0 +1,49 @@ +import { + AmbientLight, + PerspectiveCamera, + PointLight, + Scene, +} from 'three'; +import { WebGLNodesHandler } from '../../../../examples/jsm/tsl/WebGLNodesHandler.js'; + +export default QUnit.module( 'Addons', () => { + + QUnit.module( 'TSL', () => { + + QUnit.module( 'WebGLNodesHandler', () => { + + QUnit.test( 'nested render lifecycle', ( assert ) => { + + const handler = new WebGLNodesHandler(); + handler.setRenderer( { + extensions: {}, + getContext: () => ( {} ), + } ); + assert.strictEqual( handler.nodeFrame.renderer, handler.renderer, 'initializes the renderer proxy' ); + + const scene = new Scene(); + const outerCamera = new PerspectiveCamera(); + const nestedCamera = new PerspectiveCamera(); + const outerLight = new AmbientLight(); + const nestedLight = new PointLight(); + + handler.renderStart( scene, outerCamera ); + handler.updateLights( [ outerLight ] ); + handler.renderStart( scene, nestedCamera ); + handler.updateLights( [ nestedLight ] ); + handler.renderEnd(); + + const sceneContext = handler.renderStack[ 0 ].sceneContext; + assert.deepEqual( sceneContext.lightsNode.getLights(), [ outerLight ], 'restores lights for the outer render' ); + assert.strictEqual( handler.nodeFrame.camera, outerCamera, 'restores the outer camera' ); + + handler.renderEnd(); + assert.strictEqual( handler.renderStack.length, 0, 'balances the render stack' ); + + } ); + + } ); + + } ); + +} ); diff --git a/test/unit/three.addons.unit.js b/test/unit/three.addons.unit.js index dca422485f3209..b7f857b6b5c174 100644 --- a/test/unit/three.addons.unit.js +++ b/test/unit/three.addons.unit.js @@ -13,3 +13,4 @@ import './addons/loaders/SPLATLoader.tests.js'; import './addons/loaders/SPZLoader.tests.js'; import './addons/loaders/USDLoader.tests.js'; import './addons/exporters/USDZExporter.tests.js'; +import './addons/tsl/WebGLNodesHandler.tests.js';