diff --git a/examples/screenshots/webgpu_xr_shadows.jpg b/examples/screenshots/webgpu_xr_shadows.jpg index e26eb3da265d61..4361dc171136d7 100644 Binary files a/examples/screenshots/webgpu_xr_shadows.jpg and b/examples/screenshots/webgpu_xr_shadows.jpg differ diff --git a/examples/webgpu_xr_shadows.html b/examples/webgpu_xr_shadows.html index c3284e6c7dd618..fec2200d1242c3 100644 --- a/examples/webgpu_xr_shadows.html +++ b/examples/webgpu_xr_shadows.html @@ -38,7 +38,7 @@ timer.connect( document ); let camera, cameraRig, scene, renderer; - let directionalLight, torusKnot, box, icosahedron, cone; + let directionalLight, torusKnot, box, icosahedron, cone, glass; init(); @@ -132,6 +132,28 @@ cone.receiveShadow = true; scene.add( cone ); + // transmission + + glass = new THREE.Mesh( + new THREE.IcosahedronGeometry( 0.65, 4 ), + new THREE.MeshPhysicalMaterial( { + color: 0xb9e7ff, + roughness: 0.05, + metalness: 0, + transmission: 1, + thickness: 0.9, + ior: 1.45, + attenuationColor: 0x72b5d4, + attenuationDistance: 3, + specularIntensity: 1, + side: THREE.DoubleSide + } ) + ); + glass.position.set( 0.9, 1.15, 1.2 ); + glass.castShadow = true; + glass.receiveShadow = true; + scene.add( glass ); + // renderer renderer = createRenderer(); @@ -205,6 +227,9 @@ icosahedron.rotation.x += delta * 0.3; icosahedron.rotation.y -= delta * 0.5; cone.rotation.y -= delta * 0.35; + glass.position.y = 1.15 + Math.sin( elapsed * 1.1 ) * 0.08; + glass.rotation.x -= delta * 0.2; + glass.rotation.y += delta * 0.3; directionalLight.position.x = Math.cos( elapsed * 0.25 ) * 4; directionalLight.position.z = Math.sin( elapsed * 0.25 ) * 4; diff --git a/src/nodes/functions/PhysicalLightingModel.js b/src/nodes/functions/PhysicalLightingModel.js index 4e2567f4b300cb..1b0d57ef9184ba 100644 --- a/src/nodes/functions/PhysicalLightingModel.js +++ b/src/nodes/functions/PhysicalLightingModel.js @@ -13,7 +13,7 @@ import { positionViewDirection, positionView, positionWorld } from '../accessors import { Fn, float, vec2, vec3, vec4, mat3, If } from '../tsl/TSLBase.js'; import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep } from '../math/MathNode.js'; import { div } from '../math/OperatorNode.js'; -import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix } from '../accessors/Camera.js'; +import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix, cameraViewport } from '../accessors/Camera.js'; import { modelWorldMatrix } from '../accessors/ModelNode.js'; import { screenSize } from '../display/ScreenNode.js'; import { viewportMipTexture, viewportOpaqueMipTexture } from '../display/ViewportTextureNode.js'; @@ -74,10 +74,10 @@ const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture; - const transmissionSample = vTexture.sample( fragCoord ); + const transmissionSample = vTexture.sample( fragCoord.mul( cameraViewport.zw ).add( cameraViewport.xy ).div( screenSize ) ); //const transmissionSample = viewportMipTexture( fragCoord ); - const lod = log2( screenSize.x ).mul( applyIorToRoughness( roughness, ior ) ); + const lod = log2( cameraViewport.z ).mul( applyIorToRoughness( roughness, ior ) ); return textureBicubicLevel( transmissionSample, lod ); diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 47024304077842..bd46d3c33cd154 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -990,36 +990,18 @@ class WebGPUBackend extends Backend { } - // Create bundle encoders for each layer - renderContextData.bundleEncoders = []; - renderContextData.bundleSets = []; - - // Create separate bundle encoders for each camera in the array - for ( let i = 0; i < cameras.length; i ++ ) { - - const bundleEncoder = this.pipelineUtils.createBundleEncoder( - renderContext, - 'renderBundleArrayCamera_' + i - ); - - // Initialize state tracking for this bundle - const bundleSets = { - attributes: {}, - bindingGroups: [], - pipeline: null, - index: null - }; - - renderContextData.bundleEncoders.push( bundleEncoder ); - renderContextData.bundleSets.push( bundleSets ); - - } + this._createArrayCameraBundleEncoders( renderContext, renderContextData ); + renderContextData.arrayCameraRenderStages = []; // We'll complete the bundles in finishRender renderContextData.currentPass = null; } else { + renderContextData.bundleEncoders = undefined; + renderContextData.bundleSets = undefined; + renderContextData.arrayCameraRenderStages = undefined; + const currentPass = encoder.beginRenderPass( descriptor ); renderContextData.currentPass = currentPass; @@ -1046,6 +1028,62 @@ class WebGPUBackend extends Backend { } + /** + * Creates a render bundle encoder and state cache for each camera layer. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} renderContextData - The render context data. + * @private + */ + _createArrayCameraBundleEncoders( renderContext, renderContextData ) { + + const cameras = renderContext.camera.cameras; + + renderContextData.bundleEncoders = []; + renderContextData.bundleSets = []; + + for ( let i = 0; i < cameras.length; i ++ ) { + + const bundleEncoder = this.pipelineUtils.createBundleEncoder( + renderContext, + 'renderBundleArrayCamera_' + i + ); + + const bundleSets = { + attributes: {}, + bindingGroups: [], + pipeline: null, + index: null + }; + + renderContextData.bundleEncoders.push( bundleEncoder ); + renderContextData.bundleSets.push( bundleSets ); + + } + + } + + /** + * Finishes the render bundle encoders for all camera layers. + * + * @param {Object} renderContextData - The render context data. + * @return {Array} The completed render bundles. + * @private + */ + _finishArrayCameraBundleEncoders( renderContextData ) { + + const bundles = []; + + for ( const bundleEncoder of renderContextData.bundleEncoders ) { + + bundles.push( bundleEncoder.finish() ); + + } + + return bundles; + + } + /** * Creates render pass descriptors for each camera in an array camera. * @@ -1225,20 +1263,22 @@ class WebGPUBackend extends Backend { if ( this._isRenderCameraDepthArray( renderContext ) === true ) { - const bundles = []; - - for ( let i = 0; i < renderContextData.bundleEncoders.length; i ++ ) { + const bundles = this._finishArrayCameraBundleEncoders( renderContextData ); + const renderStages = renderContextData.arrayCameraRenderStages; + renderStages.push( { bundles } ); - const bundleEncoder = renderContextData.bundleEncoders[ i ]; - bundles.push( bundleEncoder.finish() ); - - } + // A viewport texture is 2D, so each layer must be captured immediately before that layer samples it. + for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) { - for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) { + const layerDescriptor = renderContextData.layerDescriptors[ i ]; + const colorLoadOps = layerDescriptor.colorAttachments.map( attachment => attachment.loadOp ); + const depthLoadOp = layerDescriptor.depthStencilAttachment?.depthLoadOp; + const stencilLoadOp = layerDescriptor.depthStencilAttachment?.stencilLoadOp; - if ( i < bundles.length ) { + for ( let stageIndex = 0; stageIndex < renderStages.length; stageIndex ++ ) { - const layerDescriptor = renderContextData.layerDescriptors[ i ]; + const renderStage = renderStages[ stageIndex ]; + const bundle = renderStage.bundles[ i ]; const renderPass = encoder.beginRenderPass( layerDescriptor ); if ( renderContext.viewport ) { @@ -1255,12 +1295,38 @@ class WebGPUBackend extends Backend { } - renderPass.executeBundles( [ bundles[ i ] ] ); + renderPass.executeBundles( [ bundle ] ); renderPass.end(); + if ( renderStage.framebufferCopy !== undefined ) { + + const { texture, sourceGPU, destinationGPU, rectangle, generateMipmaps } = renderStage.framebufferCopy; + + this._copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle, i, generateMipmaps ); + + } + + if ( stageIndex < renderStages.length - 1 ) { + + for ( const attachment of layerDescriptor.colorAttachments ) attachment.loadOp = GPULoadOp.Load; + + if ( renderContext.depth ) layerDescriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + if ( renderContext.stencil ) layerDescriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + } + } + for ( let j = 0; j < layerDescriptor.colorAttachments.length; j ++ ) { + + layerDescriptor.colorAttachments[ j ].loadOp = colorLoadOps[ j ]; + + } + + if ( renderContext.depth ) layerDescriptor.depthStencilAttachment.depthLoadOp = depthLoadOp; + if ( renderContext.stencil ) layerDescriptor.depthStencilAttachment.stencilLoadOp = stencilLoadOp; + } } else if ( renderContextData.currentPass ) { @@ -2816,6 +2882,29 @@ class WebGPUBackend extends Backend { } + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { + + // Layered draws are only executed in finishRender(), so preserve this copy as a render-stage boundary. + const bundles = this._finishArrayCameraBundleEncoders( renderContextData ); + + renderContextData.arrayCameraRenderStages.push( { + bundles, + framebufferCopy: { + texture, + sourceGPU, + destinationGPU, + rectangle: { x: rectangle.x, y: rectangle.y, z: rectangle.z, w: rectangle.w }, + // ViewportTextureNode restores this flag before the deferred copy executes. + generateMipmaps: texture.generateMipmaps + } + } ); + + this._createArrayCameraBundleEncoders( renderContext, renderContextData ); + + return; + + } + let encoder; if ( renderContextData.currentPass ) { @@ -2832,33 +2921,10 @@ class WebGPUBackend extends Backend { } - _texelCopyTextureInfoSrc.texture = sourceGPU; - _texelCopyTextureInfoSrc.origin.x = rectangle.x; - _texelCopyTextureInfoSrc.origin.y = rectangle.y; - - _texelCopyTextureInfoDst.texture = destinationGPU; - - _extent3D.width = rectangle.z; - _extent3D.height = rectangle.w; - - encoder.copyTextureToTexture( - _texelCopyTextureInfoSrc, - _texelCopyTextureInfoDst, - _extent3D - ); - - _texelCopyTextureInfoSrc.reset(); - _texelCopyTextureInfoDst.reset(); - _extent3D.reset(); - // mipmaps must be genereated with the same encoder otherwise the copied texture data // might be out-of-sync, see #31768 - if ( texture.generateMipmaps ) { - - this.textureUtils.generateMipmaps( texture, encoder ); - - } + this._copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle ); if ( renderContextData.currentPass ) { @@ -2896,6 +2962,48 @@ class WebGPUBackend extends Backend { } + /** + * Encodes a framebuffer copy from a specific texture-array layer. + * + * @param {GPUCommandEncoder} encoder - The command encoder. + * @param {Texture} texture - The destination texture. + * @param {GPUTexture} sourceGPU - The source GPU texture. + * @param {GPUTexture} destinationGPU - The destination GPU texture. + * @param {Object} rectangle - The source rectangle. + * @param {number} [sourceLayer=0] - The source array layer. + * @param {boolean} [generateMipmaps=texture.generateMipmaps] - Whether mipmaps should be generated. + * @private + */ + _copyFramebufferToTexture( encoder, texture, sourceGPU, destinationGPU, rectangle, sourceLayer = 0, generateMipmaps = texture.generateMipmaps ) { + + _texelCopyTextureInfoSrc.texture = sourceGPU; + _texelCopyTextureInfoSrc.origin.x = rectangle.x; + _texelCopyTextureInfoSrc.origin.y = rectangle.y; + _texelCopyTextureInfoSrc.origin.z = sourceLayer; + + _texelCopyTextureInfoDst.texture = destinationGPU; + + _extent3D.width = rectangle.z; + _extent3D.height = rectangle.w; + + encoder.copyTextureToTexture( + _texelCopyTextureInfoSrc, + _texelCopyTextureInfoDst, + _extent3D + ); + + _texelCopyTextureInfoSrc.reset(); + _texelCopyTextureInfoDst.reset(); + _extent3D.reset(); + + if ( generateMipmaps ) { + + this.textureUtils.generateMipmaps( texture, encoder ); + + } + + } + /** * Checks if the given compatibility is supported by the backend. *