Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified examples/screenshots/webgpu_xr_shadows.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion examples/webgpu_xr_shadows.html
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/nodes/functions/PhysicalLightingModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 );

Expand Down
226 changes: 167 additions & 59 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<GPURenderBundle>} 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.
*
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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 ) {
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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 ) {

Expand Down Expand Up @@ -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.
*
Expand Down