diff --git a/examples/jsm/tsl/shadows/TileShadowNode.js b/examples/jsm/tsl/shadows/TileShadowNode.js index 4a1179a1266b93..eb0874fafcb9ca 100644 --- a/examples/jsm/tsl/shadows/TileShadowNode.js +++ b/examples/jsm/tsl/shadows/TileShadowNode.js @@ -162,7 +162,7 @@ class TileShadowNode extends ShadowBaseNode { const depthTexture = new DepthTexture( shadowWidth, shadowHeight, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, tileCount ); depthTexture.compareFunction = LessCompare; depthTexture.name = 'ShadowDepthArrayTexture'; - const shadowMap = builder.createRenderTarget( shadowWidth, shadowHeight, { format: RedFormat, depth: tileCount } ); + const shadowMap = builder.createRenderTarget( shadowWidth, shadowHeight, { format: RedFormat, depth: tileCount, useArrayDepthTexture: true } ); shadowMap.depthTexture = depthTexture; shadowMap.texture.name = 'ShadowTexture'; this.shadowMap = shadowMap; diff --git a/examples/jsm/tsl/shadows/TileShadowNodeHelper.js b/examples/jsm/tsl/shadows/TileShadowNodeHelper.js index aaba411088fdf1..d119fa381cbc92 100644 --- a/examples/jsm/tsl/shadows/TileShadowNodeHelper.js +++ b/examples/jsm/tsl/shadows/TileShadowNodeHelper.js @@ -1,5 +1,5 @@ import { Group, NodeMaterial, Mesh, PlaneGeometry, DoubleSide, CameraHelper } from 'three/webgpu'; -import { Fn, vec4, vec3, texture, uv, positionLocal, vec2, float, screenSize } from 'three/tsl'; +import { Fn, vec4, vec3, texture, uv, positionLocal, vec2, float, int, screenSize } from 'three/tsl'; /** * Helper class to manage and display debug visuals for TileShadowNode. @@ -109,7 +109,7 @@ class TileShadowNodeHelper extends Group { const sampledDepth = texture( this.tileShadowNode.shadowMap.depthTexture ) .sample( uv().flipY() ) - .depth( float( i ) ) // Sample correct layer + .depth( int( i ) ) // Sample correct layer .compare( 0.9 ); // Example comparison value // Simple tint based on index for visual distinction diff --git a/examples/webgl_furnace_test.html b/examples/webgl_furnace_test.html index c70bff2235f9ac..713649547682fc 100644 --- a/examples/webgl_furnace_test.html +++ b/examples/webgl_furnace_test.html @@ -30,8 +30,12 @@ import * as THREE from 'three'; + import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + let scene, camera, renderer, radianceMap; + let gui; + const COLOR = 0xcccccc; function init() { @@ -47,32 +51,6 @@ renderer.setPixelRatio( window.devicePixelRatio ); document.body.appendChild( renderer.domElement ); - window.addEventListener( 'resize', onWindowResize ); - - document.body.addEventListener( 'mouseover', function () { - - scene.traverse( function ( child ) { - - if ( child.isMesh ) child.material.color.setHex( 0xffffff ); - - } ); - - render(); - - } ); - - document.body.addEventListener( 'mouseout', function () { - - scene.traverse( function ( child ) { - - if ( child.isMesh ) child.material.color.setHex( 0xccccff ); // tinted for visibility - - } ); - - render(); - - } ); - // scene scene = new THREE.Scene(); @@ -81,6 +59,12 @@ camera = new THREE.PerspectiveCamera( 40, aspect, 1, 30 ); camera.position.set( 0, 0, 18 ); + // + + initGui(); + + window.addEventListener( 'resize', onWindowResize ); + } function createObjects() { @@ -145,6 +129,30 @@ } + function initGui() { + + gui = new GUI(); + + const param = { + 'tint': false + }; + + gui.add( param, 'tint' ).name( 'Tint for Visibility' ).onChange( function ( val ) { + + scene.traverse( function ( child ) { + + const tint = val ? 0xccccff : 0xffffff; + + if ( child.isMesh ) child.material.color.setHex( tint ); + + } ); + + render(); + + } ); + + } + Promise.resolve() .then( init ) .then( createEnvironment ) diff --git a/examples/webgpu_furnace_test.html b/examples/webgpu_furnace_test.html index 438e01dbe0bd17..ba23425539ccd0 100644 --- a/examples/webgpu_furnace_test.html +++ b/examples/webgpu_furnace_test.html @@ -36,8 +36,12 @@ import * as THREE from 'three'; + import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; + let scene, camera, renderer, radianceMap; + let gui; + const COLOR = 0xcccccc; async function init() { @@ -66,31 +70,10 @@ // - window.addEventListener( 'resize', onWindowResize ); - - document.body.addEventListener( 'mouseover', function () { - - scene.traverse( function ( child ) { - - if ( child.isMesh ) child.material.color.setHex( 0xffffff ); - - } ); - - render(); - - } ); - - document.body.addEventListener( 'mouseout', function () { - - scene.traverse( function ( child ) { - - if ( child.isMesh ) child.material.color.setHex( 0xccccff ); // tinted for visibility - - } ); + initGui(); - render(); + window.addEventListener( 'resize', onWindowResize ); - } ); } function createObjects() { @@ -155,6 +138,30 @@ } + function initGui() { + + gui = new GUI(); + + const param = { + 'tint': false + }; + + gui.add( param, 'tint' ).name( 'Tint for Visibility' ).onChange( function ( val ) { + + scene.traverse( function ( child ) { + + const tint = val ? 0xccccff : 0xffffff; + + if ( child.isMesh ) child.material.color.setHex( tint ); + + } ); + + render(); + + } ); + + } + Promise.resolve() .then( init ) .then( createEnvironment ) diff --git a/src/core/BufferGeometry.js b/src/core/BufferGeometry.js index 314b229e83d046..39c7f37678bbdb 100644 --- a/src/core/BufferGeometry.js +++ b/src/core/BufferGeometry.js @@ -841,13 +841,14 @@ class BufferGeometry extends EventDispatcher { const normalAttribute = attributes.normal; const uvAttribute = attributes.uv; - if ( this.hasAttribute( 'tangent' ) === false ) { + let tangentAttribute = this.getAttribute( 'tangent' ); - this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); + if ( tangentAttribute === undefined || tangentAttribute.count !== positionAttribute.count ) { - } + tangentAttribute = new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ); + this.setAttribute( 'tangent', tangentAttribute ); - const tangentAttribute = this.getAttribute( 'tangent' ); + } const tan1 = [], tan2 = []; @@ -993,7 +994,7 @@ class BufferGeometry extends EventDispatcher { let normalAttribute = this.getAttribute( 'normal' ); - if ( normalAttribute === undefined ) { + if ( normalAttribute === undefined || normalAttribute.count !== positionAttribute.count ) { normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); this.setAttribute( 'normal', normalAttribute ); diff --git a/src/core/RenderTarget.js b/src/core/RenderTarget.js index 65626728f856c4..383a729075ce1c 100644 --- a/src/core/RenderTarget.js +++ b/src/core/RenderTarget.js @@ -36,7 +36,8 @@ class RenderTarget extends EventDispatcher { * @property {number} [samples=0] - The MSAA samples count. * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. * @property {number} [depth=1] - The texture depth. - * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering. + * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering (WebGL OVR_multiview2 extension). + * @property {boolean} [useArrayDepthTexture=false] - Whether to create the depth texture as an array texture for per-layer depth testing. This is separate from multiview so layered render targets can use array depth without the multiview extension. */ /** @@ -62,7 +63,8 @@ class RenderTarget extends EventDispatcher { samples: 0, count: 1, depth: 1, - multiview: false + multiview: false, + useArrayDepthTexture: false }, options ); /** @@ -199,6 +201,16 @@ class RenderTarget extends EventDispatcher { */ this.multiview = options.multiview; + /** + * Whether to create the depth texture as an array texture for per-layer depth testing. + * This is separate from multiview so layered render targets can use array depth without + * the multiview extension. + * + * @type {boolean} + * @default false + */ + this.useArrayDepthTexture = options.useArrayDepthTexture; + } _setTextureOptions( options = {} ) { @@ -370,6 +382,7 @@ class RenderTarget extends EventDispatcher { this.samples = source.samples; this.multiview = source.multiview; + this.useArrayDepthTexture = source.useArrayDepthTexture; return this; diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index e9aa10ed40fe4f..2b0f0f3f625028 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -1344,6 +1344,37 @@ class Renderer { } + _renderOutputLayers( quad, renderTarget ) { + + if ( renderTarget.texture.isArrayTexture !== true || renderTarget.texture.image.depth <= 1 ) { + + this._renderScene( quad, quad.camera, false ); + return; + + } + + const currentActiveCubeFace = this._activeCubeFace; + + try { + + for ( let layer = 0; layer < renderTarget.texture.image.depth; layer ++ ) { + + this._nodes.setOutputLayerIndex( layer ); + this._activeCubeFace = layer; + + this._renderScene( quad, quad.camera, false ); + + } + + } finally { + + this._nodes.setOutputLayerIndex( 0 ); + this._activeCubeFace = currentActiveCubeFace; + + } + + } + /** * Returns an internal render target which is used when computing the output tone mapping * and color space conversion. Unlike in `WebGLRenderer`, this is done in a separate render @@ -1429,6 +1460,7 @@ class Renderer { frameBufferTarget.scissor.multiplyScalar( pixelRatio ); frameBufferTarget.scissorTest = scissorTest; frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false; + frameBufferTarget.useArrayDepthTexture = outputRenderTarget !== null ? outputRenderTarget.useArrayDepthTexture : false; frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true; frameBufferTarget._autoAllocateDepthBuffer = outputRenderTarget !== null ? outputRenderTarget._autoAllocateDepthBuffer : false; @@ -1762,7 +1794,6 @@ class Renderer { // restore render tree nodeFrame.renderId = previousRenderId; - this._currentRenderContext = previousRenderContext; this._currentRenderObjectFunction = previousRenderObjectFunction; this._handleObjectFunction = previousHandleObjectFunction; @@ -1868,8 +1899,7 @@ class Renderer { this.autoClear = false; this.xr.enabled = false; - - this._renderScene( quad, quad.camera, false ); + this._renderOutputLayers( quad, renderTarget ); this.autoClear = currentAutoClear; this.xr.enabled = currentXR; diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js index bd7ac761a2fb46..5288e4d8bb91e0 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js @@ -83,6 +83,9 @@ class Textures extends DataMap { let textureNeedsUpdate = false; + const hasArrayDepthTexture = depthTexture !== undefined && depthTexture.image !== undefined && depthTexture.image.depth > 1; + const useArrayDepth = size.depth > 1 && ( renderTarget.useArrayDepthTexture || renderTarget.multiview || hasArrayDepthTexture ); + if ( depthTexture === undefined && useDepthTexture ) { depthTexture = new DepthTexture(); @@ -93,12 +96,17 @@ class Textures extends DataMap { depthTexture.image.height = mipHeight; depthTexture.image.depth = size.depth; depthTexture.renderTarget = renderTarget; - depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; depthTextureMips[ activeMipmapLevel ] = depthTexture; } + if ( depthTexture ) { + + depthTexture.isArrayTexture = useArrayDepth; + + } + if ( renderTargetData.width !== size.width || size.height !== renderTargetData.height ) { textureNeedsUpdate = true; @@ -108,7 +116,7 @@ class Textures extends DataMap { depthTexture.needsUpdate = true; depthTexture.image.width = mipWidth; depthTexture.image.height = mipHeight; - depthTexture.image.depth = depthTexture.isArrayTexture ? depthTexture.image.depth : 1; + depthTexture.image.depth = useArrayDepth ? size.depth : 1; } diff --git a/src/renderers/common/nodes/NodeManager.js b/src/renderers/common/nodes/NodeManager.js index 2caf90c8efea77..7d40e968c80bf9 100644 --- a/src/renderers/common/nodes/NodeManager.js +++ b/src/renderers/common/nodes/NodeManager.js @@ -4,7 +4,7 @@ import NodeBuilderState from './NodeBuilderState.js'; import NodeMaterial from '../../../materials/nodes/NodeMaterial.js'; import { cubeMapNode } from '../../../nodes/utils/CubeMapNode.js'; import { NodeFrame, StackTrace } from '../../../nodes/Nodes.js'; -import { renderGroup, cubeTexture, texture, fog, rangeFogFactor, densityFogFactor, reference, pmremTexture, screenUV } from '../../../nodes/TSL.js'; +import { renderGroup, cubeTexture, texture, fog, rangeFogFactor, densityFogFactor, reference, pmremTexture, screenUV, uniform } from '../../../nodes/TSL.js'; import { builtin } from '../../../nodes/accessors/BuiltinNode.js'; import { CubeUVReflectionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js'; @@ -14,6 +14,10 @@ import { error } from '../../../utils.js'; const _chainKeys = []; const _cacheKeyValues = []; +// Dedicated uniform for output pass array layer selection +// This is separate from cameraIndex to avoid the sharedUniformGroup complexity +const _outputLayerIndex = /*@__PURE__*/ uniform( 0, 'int' ).setGroup( renderGroup ); + /** * This renderer module manages node-related objects and is the * primary interface between the renderer and the node system. @@ -864,14 +868,42 @@ class NodeManager extends DataMap { const renderer = this.renderer; - const output = outputTarget.isArrayTexture ? - texture( outputTarget, screenUV ).depth( builtin( 'gl_ViewID_OVR' ) ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ) : - texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + let output; + + if ( outputTarget.isArrayTexture ) { + + if ( this.backend.isWebGLBackend ) { + + output = texture( outputTarget, screenUV ).depth( builtin( 'gl_ViewID_OVR' ) ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + + } else { + + output = texture( outputTarget, screenUV ).depth( _outputLayerIndex ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + + } + + } else { + + output = texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + + } return output; } + /** + * Sets the output layer index for array texture output pass. + * This should be called before each layer render during the output pass. + * + * @param {number} index - The layer index. + */ + setOutputLayerIndex( index ) { + + _outputLayerIndex.value = index; + + } + /** * Triggers the call of `updateBefore()` methods * for all nodes of the given render object. diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index bb055354ebd5d9..cfbb7256027796 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -436,7 +436,7 @@ class WebGPUBackend extends Backend { } /** - * Internal to determine if the current render target is a render target array with depth 2D array texture. + * Returns whether the render target is a render target array with depth 2D array texture. * * @param {RenderContext} renderContext - The render context. * @return {boolean} Whether the render target is a render target array with depth 2D array texture. @@ -445,7 +445,9 @@ class WebGPUBackend extends Backend { */ _isRenderCameraDepthArray( renderContext ) { - return renderContext.depthTexture && renderContext.depthTexture.image.depth > 1 && renderContext.camera.isArrayCamera; + const camera = renderContext.camera; + + return renderContext.depthTexture && renderContext.depthTexture.isArrayTexture === true && camera !== null && camera.isArrayCamera === true; } @@ -764,7 +766,7 @@ class WebGPUBackend extends Backend { } - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; } @@ -781,7 +783,7 @@ class WebGPUBackend extends Backend { } - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; } @@ -789,7 +791,7 @@ class WebGPUBackend extends Backend { const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } ); - // shadow arrays - prepare bundle encoders for each camera in an array camera + // Layered render targets: prepare bundle encoders for each camera in the array camera. if ( this._isRenderCameraDepthArray( renderContext ) === true ) { @@ -797,11 +799,11 @@ class WebGPUBackend extends Backend { if ( ! renderContextData.layerDescriptors || renderContextData.layerDescriptors.length !== cameras.length ) { - this._createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ); + this._createArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras ); } else { - this._updateDepthLayerDescriptors( renderContext, renderContextData, cameras ); + this._updateArrayCameraLayerDescriptors( renderContext, renderContextData, cameras ); } @@ -862,8 +864,7 @@ class WebGPUBackend extends Backend { } /** - * This method creates layer descriptors for each camera in an array camera - * to prepare for rendering to a depth array texture. + * Creates render pass descriptors for each camera in an array camera. * * @param {RenderContext} renderContext - The render context. * @param {Object} renderContextData - The render context data. @@ -872,7 +873,7 @@ class WebGPUBackend extends Backend { * * @private */ - _createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) { + _createArrayCameraLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) { const depthStencilAttachment = descriptor.depthStencilAttachment; renderContextData.layerDescriptors = []; @@ -936,15 +937,14 @@ class WebGPUBackend extends Backend { } /** - * This method updates the layer descriptors for each camera in an array camera - * to prepare for rendering to a depth array texture. + * Updates render pass descriptors for each camera in an array camera. * * @param {RenderContext} renderContext - The render context. * @param {Object} renderContextData - The render context data. * @param {ArrayCamera} cameras - The array camera. * */ - _updateDepthLayerDescriptors( renderContext, renderContextData, cameras ) { + _updateArrayCameraLayerDescriptors( renderContext, renderContextData, cameras ) { for ( let i = 0; i < cameras.length; i ++ ) { @@ -1013,7 +1013,7 @@ class WebGPUBackend extends Backend { } - // shadow arrays - Execute bundles for each layer + // Layered render targets: execute the bundle for each layer. const encoder = renderContextData.encoder; @@ -1766,7 +1766,9 @@ class WebGPUBackend extends Backend { let pass = renderContextData.currentPass; let sets = renderContextData.currentSets; - if ( renderContextData.bundleEncoders ) { + const isBundleEncoder = renderContextData.bundleEncoders !== undefined; + + if ( isBundleEncoder ) { const bundleEncoder = renderContextData.bundleEncoders[ i ]; const bundleSets = renderContextData.bundleSets[ i ]; @@ -1774,8 +1776,9 @@ class WebGPUBackend extends Backend { sets = bundleSets; } + // GPURenderBundleEncoder does not support setViewport, only GPURenderPassEncoder does - if ( vp ) { + if ( vp && ! isBundleEncoder ) { pass.setViewport( Math.floor( vp.x * pixelRatio ), diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index 8bf4dfadf0e454..f873f543585a29 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -327,14 +327,16 @@ class WebGPUBindingUtils { dimensionViewGPU = GPUTextureViewDimension.Cube; - } else if ( binding.isSampledTexture3D ) { - - dimensionViewGPU = GPUTextureViewDimension.ThreeD; - } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + // Prefer the texture's actual array flag over the cached 3D binding type. + // Layered render targets can become array textures after shader compilation. dimensionViewGPU = GPUTextureViewDimension.TwoDArray; + } else if ( binding.isSampledTexture3D ) { + + dimensionViewGPU = GPUTextureViewDimension.ThreeD; + } else { dimensionViewGPU = GPUTextureViewDimension.TwoD;