Skip to content

Commit 1e9b741

Browse files
cabanierMugen87
authored andcommitted
Multiview support for webgpu renderer (mrdoob#30920)
* Add support for multiview * support multi-pass with webxr multiview * update fragment shader for multiview * Clean up. * Update XRManager.js * address review comment for multiview rendering * more cleanup + fixes for multiview * fix webxr layers bug + enabled multiview in native layers sample * Update WebGLTextureUtils.js * Update RenderTarget.js * remove test code * fix reference space for non-layers path --------- Co-authored-by: Mugen87 <[email protected]>
1 parent 6eed913 commit 1e9b741

File tree

16 files changed

+195
-40
lines changed

16 files changed

+195
-40
lines changed

examples/webgpu_xr_cubes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696

9797
raycaster = new THREE.Raycaster();
9898

99-
renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, colorBufferType: THREE.UnsignedByteType } );
99+
renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, colorBufferType: THREE.UnsignedByteType, multiview: true } );
100100
renderer.setPixelRatio( window.devicePixelRatio );
101101
renderer.setSize( window.innerWidth, window.innerHeight );
102102
renderer.setAnimationLoop( animate );

examples/webgpu_xr_native_layers.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180

181181
//
182182

183-
renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, colorBufferType: THREE.UnsignedByteType } );
183+
renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, colorBufferType: THREE.UnsignedByteType, multiview: true } );
184184
renderer.setPixelRatio( window.devicePixelRatio );
185185
renderer.setSize( window.innerWidth, window.innerHeight );
186186
renderer.setAnimationLoop( render );

src/cameras/ArrayCamera.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,21 @@ class ArrayCamera extends PerspectiveCamera {
3131
*/
3232
this.isArrayCamera = true;
3333

34+
/**
35+
* Whether this camera is used with multiview rendering or not.
36+
*
37+
* @type {boolean}
38+
* @readonly
39+
* @default false
40+
*/
41+
this.isMultiViewCamera = false;
42+
3443
/**
3544
* An array of perspective sub cameras.
3645
*
3746
* @type {Array<PerspectiveCamera>}
3847
*/
3948
this.cameras = array;
40-
this.index = 0;
4149

4250
}
4351

src/core/RenderTarget.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class RenderTarget extends EventDispatcher {
3535
* @property {?Texture} [depthTexture=null] - Reference to a depth texture.
3636
* @property {number} [samples=0] - The MSAA samples count.
3737
* @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`.
38+
* @property {boolean} [multiview=false] - Whether this target is used for multiview rendering.
3839
*/
3940

4041
/**
@@ -79,7 +80,7 @@ class RenderTarget extends EventDispatcher {
7980
* @type {number}
8081
* @default 1
8182
*/
82-
this.depth = 1;
83+
this.depth = options.depth ? options.depth : 1;
8384

8485
/**
8586
* A rectangular area inside the render target's viewport. Fragments that are
@@ -107,7 +108,7 @@ class RenderTarget extends EventDispatcher {
107108
*/
108109
this.viewport = new Vector4( 0, 0, width, height );
109110

110-
const image = { width: width, height: height, depth: 1 };
111+
const image = { width: width, height: height, depth: this.depth };
111112

112113
options = Object.assign( {
113114
generateMipmaps: false,
@@ -119,7 +120,8 @@ class RenderTarget extends EventDispatcher {
119120
resolveStencilBuffer: true,
120121
depthTexture: null,
121122
samples: 0,
122-
count: 1
123+
count: 1,
124+
multiview: false
123125
}, options );
124126

125127
const texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace );
@@ -190,6 +192,14 @@ class RenderTarget extends EventDispatcher {
190192
*/
191193
this.samples = options.samples;
192194

195+
/**
196+
* Whether to this target is used in multiview rendering.
197+
*
198+
* @type {boolean}
199+
* @default false
200+
*/
201+
this.multiview = options.multiview;
202+
193203
}
194204

195205
/**

src/nodes/accessors/Camera.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { renderGroup, sharedUniformGroup } from '../core/UniformGroupNode.js';
33
import { Vector3 } from '../../math/Vector3.js';
44
import { Fn } from '../tsl/TSLBase.js';
55
import { uniformArray } from './UniformArrayNode.js';
6+
import { builtin } from './BuiltinNode.js';
67

78
/**
89
* TSL object that represents the current `index` value of the camera if used ArrayCamera.
@@ -50,7 +51,7 @@ export const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
5051

5152
const cameraProjectionMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatrices' );
5253

53-
cameraProjectionMatrix = cameraProjectionMatrices.element( cameraIndex ).toVar( 'cameraProjectionMatrix' );
54+
cameraProjectionMatrix = cameraProjectionMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrix' );
5455

5556
} else {
5657

@@ -84,7 +85,7 @@ export const cameraProjectionMatrixInverse = /*@__PURE__*/ ( Fn( ( { camera } )
8485

8586
const cameraProjectionMatricesInverse = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatricesInverse' );
8687

87-
cameraProjectionMatrixInverse = cameraProjectionMatricesInverse.element( cameraIndex ).toVar( 'cameraProjectionMatrixInverse' );
88+
cameraProjectionMatrixInverse = cameraProjectionMatricesInverse.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrixInverse' );
8889

8990
} else {
9091

@@ -118,7 +119,7 @@ export const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => {
118119

119120
const cameraViewMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraViewMatrices' );
120121

121-
cameraViewMatrix = cameraViewMatrices.element( cameraIndex ).toVar( 'cameraViewMatrix' );
122+
cameraViewMatrix = cameraViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraViewMatrix' );
122123

123124
} else {
124125

src/renderers/common/Renderer.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Renderer {
5757
* @property {?Function} [getFallback=null] - This callback function can be used to provide a fallback backend, if the primary backend can't be targeted.
5858
* @property {number} [colorBufferType=HalfFloatType] - Defines the type of color buffers. The default `HalfFloatType` is recommend for best
5959
* quality. To save memory and bandwidth, `UnsignedByteType` might be used. This will reduce rendering quality though.
60+
* @property {boolean} [multiview=false] - If set to `true`, the renderer will use multiview during WebXR rendering if supported.
6061
*/
6162

6263
/**
@@ -87,7 +88,8 @@ class Renderer {
8788
antialias = false,
8889
samples = 0,
8990
getFallback = null,
90-
colorBufferType = HalfFloatType
91+
colorBufferType = HalfFloatType,
92+
multiview = false
9193
} = parameters;
9294

9395
/**
@@ -679,7 +681,7 @@ class Renderer {
679681
*
680682
* @type {XRManager}
681683
*/
682-
this.xr = new XRManager( this );
684+
this.xr = new XRManager( this, multiview );
683685

684686
/**
685687
* Debug configuration.
@@ -1190,14 +1192,17 @@ class Renderer {
11901192

11911193
}
11921194

1195+
const outputRenderTarget = this.getOutputRenderTarget();
1196+
11931197
frameBufferTarget.depthBuffer = depth;
11941198
frameBufferTarget.stencilBuffer = stencil;
1195-
frameBufferTarget.setSize( width, height );
1199+
frameBufferTarget.setSize( width, height, outputRenderTarget !== null ? outputRenderTarget.depth : 1 );
11961200
frameBufferTarget.viewport.copy( this._viewport );
11971201
frameBufferTarget.scissor.copy( this._scissor );
11981202
frameBufferTarget.viewport.multiplyScalar( this._pixelRatio );
11991203
frameBufferTarget.scissor.multiplyScalar( this._pixelRatio );
12001204
frameBufferTarget.scissorTest = this._scissorTest;
1205+
frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false;
12011206

12021207
return frameBufferTarget;
12031208

src/renderers/common/Textures.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import DataMap from './DataMap.js';
22

33
import { Vector3 } from '../../math/Vector3.js';
44
import { DepthTexture } from '../../textures/DepthTexture.js';
5+
import { DepthArrayTexture } from '../../textures/DepthArrayTexture.js';
56
import { DepthStencilFormat, DepthFormat, UnsignedIntType, UnsignedInt248Type, UnsignedByteType } from '../../constants.js';
67

78
const _size = /*@__PURE__*/ new Vector3();
@@ -76,11 +77,21 @@ class Textures extends DataMap {
7677

7778
if ( depthTexture === undefined && useDepthTexture ) {
7879

79-
depthTexture = new DepthTexture();
80+
if ( renderTarget.multiview === true && size.depth > 1 ) {
81+
82+
depthTexture = new DepthArrayTexture();
83+
84+
} else {
85+
86+
depthTexture = new DepthTexture();
87+
88+
}
89+
8090
depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat;
8191
depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType
8292
depthTexture.image.width = mipWidth;
8393
depthTexture.image.height = mipHeight;
94+
depthTexture.image.depth = size.depth;
8495

8596
depthTextureMips[ activeMipmapLevel ] = depthTexture;
8697

@@ -136,6 +147,7 @@ class Textures extends DataMap {
136147

137148
const texture = textures[ i ];
138149

150+
texture.isTextureArray = renderTarget.multiview === true && size.depth > 1;
139151
if ( textureNeedsUpdate ) texture.needsUpdate = true;
140152

141153
this.updateTexture( texture, options );

src/renderers/common/XRManager.js

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Vector4 } from '../../math/Vector4.js';
99
import { WebXRController } from '../webxr/WebXRController.js';
1010
import { AddEquation, BackSide, CustomBlending, DepthFormat, DepthStencilFormat, FrontSide, RGBAFormat, UnsignedByteType, UnsignedInt248Type, UnsignedIntType, ZeroFactor } from '../../constants.js';
1111
import { DepthTexture } from '../../textures/DepthTexture.js';
12+
import { DepthArrayTexture } from '../../textures/DepthArrayTexture.js';
1213
import { XRRenderTarget } from './XRRenderTarget.js';
1314
import { CylinderGeometry } from '../../geometries/CylinderGeometry.js';
1415
import { PlaneGeometry } from '../../geometries/PlaneGeometry.js';
@@ -32,8 +33,9 @@ class XRManager extends EventDispatcher {
3233
* Constructs a new XR manager.
3334
*
3435
* @param {Renderer} renderer - The renderer.
36+
* @param {boolean} [multiview=false] - Enables multiview if the device supports it.
3537
*/
36-
constructor( renderer ) {
38+
constructor( renderer, multiview = false ) {
3739

3840
super();
3941

@@ -354,6 +356,26 @@ class XRManager extends EventDispatcher {
354356
*/
355357
this._useLayers = ( typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat
356358

359+
/**
360+
* Whether the usage of multiview has been requested by the application or not.
361+
*
362+
* @private
363+
* @type {boolean}
364+
* @default false
365+
* @readonly
366+
*/
367+
this._useMultiviewIfPossible = multiview;
368+
369+
/**
370+
* Whether the usage of multiview is actually enabled. This flag only evaluates to `true`
371+
* if multiview has been requested by the application and the `OVR_multiview2` is available.
372+
*
373+
* @private
374+
* @type {boolean}
375+
* @readonly
376+
*/
377+
this._useMultiview = false;
378+
357379
}
358380

359381
/**
@@ -564,6 +586,17 @@ class XRManager extends EventDispatcher {
564586

565587
}
566588

589+
/**
590+
* Returns `true` if the engine renders to a multiview target.
591+
*
592+
* @return {boolean} Whether the engine renders to a multiview render target or not.
593+
*/
594+
useMultiview() {
595+
596+
return this._useMultiview;
597+
598+
}
599+
567600
createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = [] ) {
568601

569602
const geometry = new PlaneGeometry( width, height );
@@ -825,6 +858,13 @@ class XRManager extends EventDispatcher {
825858
scaleFactor: this._framebufferScaleFactor
826859
};
827860

861+
if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2' ) ) {
862+
863+
projectionlayerInit.textureType = 'texture-array';
864+
this._useMultiview = true;
865+
866+
}
867+
828868
const glBinding = new XRWebGLBinding( session, gl );
829869
const glProjLayer = glBinding.createProjectionLayer( projectionlayerInit );
830870
const layersArray = [ glProjLayer ];
@@ -835,24 +875,43 @@ class XRManager extends EventDispatcher {
835875
renderer.setPixelRatio( 1 );
836876
renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false );
837877

878+
let depthTexture;
879+
if ( this._useMultiview ) {
880+
881+
depthTexture = new DepthArrayTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, 2 );
882+
depthTexture.type = depthType;
883+
depthTexture.format = depthFormat;
884+
885+
} else {
886+
887+
depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat );
888+
889+
}
890+
891+
838892
this._xrRenderTarget = new XRRenderTarget(
839893
glProjLayer.textureWidth,
840894
glProjLayer.textureHeight,
841895
{
842896
format: RGBAFormat,
843897
type: UnsignedByteType,
844898
colorSpace: renderer.outputColorSpace,
845-
depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ),
899+
depthTexture: depthTexture,
846900
stencilBuffer: renderer.stencil,
847901
samples: attributes.antialias ? 4 : 0,
848902
resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ),
849903
resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ),
904+
depth: this._useMultiview ? 2 : 1,
905+
multiview: this._useMultiview
850906
} );
851907

852908
this._xrRenderTarget.hasExternalTextures = true;
909+
this._xrRenderTarget.depth = this._useMultiview ? 2 : 1;
853910

854911
this._supportsLayers = session.enabledFeatures.includes( 'layers' );
855912

913+
this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
914+
856915
if ( this._supportsLayers ) {
857916

858917
// switch layers to native
@@ -908,14 +967,14 @@ class XRManager extends EventDispatcher {
908967
}
909968
);
910969

970+
this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
971+
911972
}
912973

913974
//
914975

915976
this.setFoveation( this.getFoveation() );
916977

917-
this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
918-
919978
renderer._animation.setAnimationLoop( this._onAnimationFrame );
920979
renderer._animation.setContext( session );
921980
renderer._animation.start();
@@ -950,6 +1009,7 @@ class XRManager extends EventDispatcher {
9501009

9511010
cameraXR.near = cameraR.near = cameraL.near = depthNear;
9521011
cameraXR.far = cameraR.far = cameraL.far = depthFar;
1012+
cameraXR.isMultiViewCamera = this._useMultiview;
9531013

9541014
if ( this._currentDepthNear !== cameraXR.near || this._currentDepthFar !== cameraXR.far ) {
9551015

@@ -1261,6 +1321,7 @@ function onSessionEnd() {
12611321
//
12621322

12631323
this.isPresenting = false;
1324+
this._useMultiview = false;
12641325

12651326
renderer._animation.stop();
12661327

@@ -1431,7 +1492,7 @@ function onAnimationFrame( time, frame ) {
14311492
backend.setXRRenderTargetTextures(
14321493
this._xrRenderTarget,
14331494
glSubImage.colorTexture,
1434-
this._glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture
1495+
( this._glProjLayer.ignoreDepthValues && ! this._useMultiview ) ? undefined : glSubImage.depthStencilTexture
14351496
);
14361497

14371498
}

0 commit comments

Comments
 (0)