From d4b5a81dd7754282aecf70dbc2c27403aa70dd44 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Wed, 29 Jul 2026 10:53:19 +0200 Subject: [PATCH] WebGPURenderer: Fix transmission during WebXR rendering (#34114) --- examples/screenshots/webgpu_xr_shadows.jpg | Bin 21467 -> 21382 bytes examples/webgpu_xr_shadows.html | 27 ++- src/nodes/functions/PhysicalLightingModel.js | 6 +- src/renderers/webgpu/WebGPUBackend.js | 226 ++++++++++++++----- 4 files changed, 196 insertions(+), 63 deletions(-) diff --git a/examples/screenshots/webgpu_xr_shadows.jpg b/examples/screenshots/webgpu_xr_shadows.jpg index e26eb3da265d61f4c57646d23f7e9113f0f8948a..4361dc171136d785c40001e2a00a23c692605385 100644 GIT binary patch delta 14683 zcmYkCby(Bi`~T+~6#-!)BF&VPmXc;FA_BssB_|*t-3@!GNJvTx0+Wsr(#@n>q&r48 zqX!$~$EUv6@2~AT*LD8b&V8Qu^E~e7bndmF+-tw)?f|Xms7uf-KS#WXpZb{mUB7p~ z)MWGq9c1eR?>anfo9|>w*V79B*7|_?)5WuR%kQ#l*KS+h*ZVS-I_^do9z@2N zVdJrqNpA{@I4_J2eM>@;%`EKfR==O8JR;^El__(%Mc+G-100p`Yh#n5}(W*++i;yOQ1CT0U>xN`X=zkx$1u{OB+y6w^YF<<+PSE3M8dg2N0BdpQrzf;(C4mvAD2J^YUg}(p__05DHIY!>Ubf8VHtQub-tFyylX-kgr@>Jm{gX zIZ%;K{qAS+T4ps&j;{@h)klXlQDBq3%ohTS>TI^N9mFr53yBL{g0{kyRy_7<4>BR{ z<E(?ZnTV2%1B0{lrJA9vM|fFjquKYDAjR>~jpcz@gb=|3SBZb- zH3kovXKH$AgWwv=4pBUr)m*5Je4tGIvVmXYbKmm^

*7-6dmXeMrG>ED9Wuli%!S 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 delta 14743 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 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. *