Skip to content

Commit 8d5eafc

Browse files
committed
Add support for multiview
1 parent fec7a6c commit 8d5eafc

File tree

13 files changed

+145
-19
lines changed

13 files changed

+145
-19
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, useMultiview: true } );
100100
renderer.setPixelRatio( window.devicePixelRatio );
101101
renderer.setSize( window.innerWidth, window.innerHeight );
102102
renderer.setAnimationLoop( animate );

src/cameras/ArrayCamera.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class ArrayCamera extends PerspectiveCamera {
3131
*/
3232
this.isArrayCamera = true;
3333

34+
this.isMultiViewCamera = false;
35+
3436
/**
3537
* An array of perspective sub cameras.
3638
*

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ class Renderer {
679679
*
680680
* @type {XRManager}
681681
*/
682-
this.xr = new XRManager( this );
682+
this.xr = new XRManager( this, parameters.useMultiview );
683683

684684
/**
685685
* Debug configuration.

src/renderers/common/XRManager.js

Lines changed: 56 additions & 4 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';
@@ -33,7 +34,7 @@ class XRManager extends EventDispatcher {
3334
*
3435
* @param {Renderer} renderer - The renderer.
3536
*/
36-
constructor( renderer ) {
37+
constructor( renderer, enablemultiviewifpossible = false ) {
3738

3839
super();
3940

@@ -354,6 +355,24 @@ class XRManager extends EventDispatcher {
354355
*/
355356
this._useLayers = ( typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat
356357

358+
/**
359+
* Whether to use the multiview extension or not.
360+
*
361+
* @private
362+
* @type {boolean}
363+
* @readonly
364+
*/
365+
this._useMultiviewIfPossible = enablemultiviewifpossible;
366+
367+
/**
368+
* Whether to use the multiview extension was enabled.
369+
*
370+
* @private
371+
* @type {boolean}
372+
* @readonly
373+
*/
374+
this._usesMultiview = false;
375+
357376
}
358377

359378
/**
@@ -564,6 +583,16 @@ class XRManager extends EventDispatcher {
564583

565584
}
566585

586+
/* Returns if we are rendering to a multiview target.
587+
*
588+
* @return {'bool'} The state of rendering to multiview.
589+
*/
590+
usesMultiview() {
591+
592+
return this._usesMultiview;
593+
594+
}
595+
567596
createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = [] ) {
568597

569598
const geometry = new PlaneGeometry( width, height );
@@ -819,12 +848,19 @@ class XRManager extends EventDispatcher {
819848

820849
}
821850

822-
const projectionlayerInit = {
851+
let projectionlayerInit = {
823852
colorFormat: gl.RGBA8,
824853
depthFormat: glDepthFormat,
825854
scaleFactor: this._framebufferScaleFactor
826855
};
827856

857+
if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2') ) {
858+
859+
projectionlayerInit.textureType = 'texture-array';
860+
this._usesMultiview = true;
861+
862+
}
863+
828864
const glBinding = new XRWebGLBinding( session, gl );
829865
const glProjLayer = glBinding.createProjectionLayer( projectionlayerInit );
830866
const layersArray = [ glProjLayer ];
@@ -835,21 +871,36 @@ class XRManager extends EventDispatcher {
835871
renderer.setPixelRatio( 1 );
836872
renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false );
837873

874+
let depthTexture;
875+
if ( this._usesMultiview ) {
876+
877+
depthTexture = new DepthArrayTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, 2 );
878+
depthTexture.type = depthType;
879+
depthTexture.format = depthFormat;
880+
881+
} else {
882+
883+
depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat );
884+
885+
}
886+
887+
838888
this._xrRenderTarget = new XRRenderTarget(
839889
glProjLayer.textureWidth,
840890
glProjLayer.textureHeight,
841891
{
842892
format: RGBAFormat,
843893
type: UnsignedByteType,
844894
colorSpace: renderer.outputColorSpace,
845-
depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ),
895+
depthTexture: depthTexture,
846896
stencilBuffer: renderer.stencil,
847897
samples: attributes.antialias ? 4 : 0,
848898
resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ),
849899
resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ),
850900
} );
851901

852902
this._xrRenderTarget.hasExternalTextures = true;
903+
this._xrRenderTarget.usesMultiview = this._usesMultiview;
853904

854905
this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() );
855906

@@ -950,6 +1001,7 @@ class XRManager extends EventDispatcher {
9501001

9511002
cameraXR.near = cameraR.near = cameraL.near = depthNear;
9521003
cameraXR.far = cameraR.far = cameraL.far = depthFar;
1004+
cameraXR.isMultiViewCamera = this._usesMultiview;
9531005

9541006
if ( this._currentDepthNear !== cameraXR.near || this._currentDepthFar !== cameraXR.far ) {
9551007

@@ -1431,7 +1483,7 @@ function onAnimationFrame( time, frame ) {
14311483
backend.setXRRenderTargetTextures(
14321484
this._xrRenderTarget,
14331485
glSubImage.colorTexture,
1434-
this._glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture
1486+
( this._glProjLayer.ignoreDepthValues && !this._usesMultiview ) ? undefined : glSubImage.depthStencilTexture
14351487
);
14361488

14371489
}

src/renderers/common/XRRenderTarget.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class XRRenderTarget extends RenderTarget {
5555
*/
5656
this.autoAllocateDepthBuffer = true;
5757

58+
this.usesMultiview = false;
59+
5860
}
5961

6062
copy( source ) {

src/renderers/common/nodes/Nodes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ class Nodes extends DataMap {
201201
nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
202202
nodeBuilder.fogNode = this.getFogNode( renderObject.scene );
203203
nodeBuilder.clippingContext = renderObject.clippingContext;
204+
if (this.renderer.xr.usesMultiview()) {
205+
nodeBuilder.enableMultiview();
206+
}
204207
nodeBuilder.build();
205208

206209
nodeBuilderState = this._createNodeBuilderState( nodeBuilder );

src/renderers/webgl-fallback/WebGLBackend.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ class WebGLBackend extends Backend {
264264
this.extensions.get( 'WEBGL_multisampled_render_to_texture' );
265265
this.extensions.get( 'WEBGL_render_shared_exponent' );
266266
this.extensions.get( 'WEBGL_multi_draw' );
267+
this.extensions.get( 'OVR_multiview2' );
267268

268269
this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' );
269270
this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' );
@@ -360,7 +361,7 @@ class WebGLBackend extends Backend {
360361

361362
// The multisample_render_to_texture extension doesn't work properly if there
362363
// are midframe flushes and an external depth texture.
363-
if ( ( this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) && renderTarget.autoAllocateDepthBuffer ) {
364+
if ( ( this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) && renderTarget.autoAllocateDepthBuffer && !renderTarget.usesMultiview ) {
364365

365366
console.warn( 'THREE.WebGLBackend: Render-to-texture extension was disabled because an external texture was provided' );
366367

@@ -547,7 +548,7 @@ class WebGLBackend extends Backend {
547548

548549
const { samples } = renderContext.renderTarget;
549550

550-
if ( samples > 0 && this._useMultisampledRTT( renderContext.renderTarget ) === false ) {
551+
if ( samples > 0 && this._usesMultisampledExtension( renderContext.renderTarget ) === false ) {
551552

552553
const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ];
553554

@@ -1142,7 +1143,7 @@ class WebGLBackend extends Backend {
11421143

11431144
};
11441145

1145-
if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) {
1146+
if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 && !renderObject.camera.isMultiViewCamera) {
11461147

11471148
const cameraData = this.get( renderObject.camera );
11481149
const cameras = renderObject.camera.cameras;
@@ -1998,7 +1999,8 @@ class WebGLBackend extends Backend {
19981999
let msaaFb = renderTargetContextData.msaaFrameBuffer;
19992000
let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
20002001
const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' );
2001-
const useMultisampledRTT = this._useMultisampledRTT( renderTarget );
2002+
const multiviewExt = this.extensions.get( 'OVR_multiview2' );
2003+
const useMultisampledRTT = this._usesMultisampledExtension( renderTarget );
20022004

20032005
const cacheKey = getCacheKey( descriptor );
20042006

@@ -2061,7 +2063,11 @@ class WebGLBackend extends Backend {
20612063

20622064
} else {
20632065

2064-
if ( hasExternalTextures && useMultisampledRTT ) {
2066+
if ( hasExternalTextures && this.renderer.xr.usesMultiview() ) {
2067+
2068+
multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, samples, 0, 2 );
2069+
2070+
} else if ( hasExternalTextures && useMultisampledRTT ) {
20652071

20662072
multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
20672073

@@ -2094,7 +2100,11 @@ class WebGLBackend extends Backend {
20942100
textureData.renderTarget = descriptor.renderTarget;
20952101
textureData.cacheKey = cacheKey; // required for copyTextureToTexture()
20962102

2097-
if ( hasExternalTextures && useMultisampledRTT ) {
2103+
if ( hasExternalTextures && this.renderer.xr.usesMultiview() ) {
2104+
2105+
multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 );
2106+
2107+
} else if ( hasExternalTextures && useMultisampledRTT ) {
20982108

20992109
multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
21002110

@@ -2151,7 +2161,11 @@ class WebGLBackend extends Backend {
21512161

21522162
const textureData = this.get( descriptor.textures[ 0 ] );
21532163

2154-
if ( useMultisampledRTT ) {
2164+
if ( this.renderer.xr.usesMultiview() ) {
2165+
2166+
multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, textureData.textureGPU, 0, samples, 0, 2 );
2167+
2168+
} else if ( useMultisampledRTT ) {
21552169

21562170
multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
21572171

@@ -2175,7 +2189,11 @@ class WebGLBackend extends Backend {
21752189

21762190
const textureData = this.get( descriptor.depthTexture );
21772191

2178-
if ( useMultisampledRTT ) {
2192+
if ( this.renderer.xr.usesMultiview() ) {
2193+
2194+
multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 );
2195+
2196+
} else if ( useMultisampledRTT ) {
21792197

21802198
multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples );
21812199

@@ -2191,7 +2209,7 @@ class WebGLBackend extends Backend {
21912209

21922210
}
21932211

2194-
if ( samples > 0 && useMultisampledRTT === false ) {
2212+
if ( samples > 0 && useMultisampledRTT === false && !this.renderer.xr.usesMultiview ) {
21952213

21962214
if ( msaaFb === undefined ) {
21972215

@@ -2484,7 +2502,13 @@ class WebGLBackend extends Backend {
24842502
* @param {RenderTarget} renderTarget - The render target that should be multisampled.
24852503
* @return {boolean} Whether to use the `WEBGL_multisampled_render_to_texture` extension for MSAA or not.
24862504
*/
2487-
_useMultisampledRTT( renderTarget ) {
2505+
_usesMultisampledExtension( renderTarget ) {
2506+
2507+
if ( renderTarget.usesMultiview ) {
2508+
2509+
return true;
2510+
2511+
}
24882512

24892513
return renderTarget.samples > 0 && this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTarget.autoAllocateDepthBuffer !== false;
24902514

src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,17 @@ ${ flowData.code }
946946

947947
}
948948

949+
/**
950+
* Returns the view id builtin for multiview.
951+
*
952+
* @return {string} The ViewID builtin.
953+
*/
954+
getViewID() {
955+
956+
return 'gl_ViewID_OVR';
957+
958+
}
959+
949960
/**
950961
* Enables the given extension.
951962
*
@@ -1091,6 +1102,20 @@ ${ flowData.code }
10911102

10921103
}
10931104

1105+
/**
1106+
* Enables multisample multiview.
1107+
*
1108+
* @param {string} planeCount - The clipping plane count.
1109+
*/
1110+
enableMultiview() {
1111+
1112+
this.enableExtension( 'GL_OVR_multiview2', 'require', 'fragment' );
1113+
this.enableExtension( 'GL_OVR_multiview2', 'require', 'vertex' );
1114+
1115+
this.builtins[ 'vertex' ].push( `layout(num_views = 2) in` );
1116+
1117+
}
1118+
10941119
/**
10951120
* Registers a transform in context of Transform Feedback.
10961121
*
@@ -1207,6 +1232,9 @@ void main() {
12071232
12081233
${ this.getSignature() }
12091234
1235+
// extensions
1236+
${shaderData.extensions}
1237+
12101238
// precision
12111239
${ defaultPrecisions }
12121240

src/renderers/webgl-fallback/utils/WebGLConstants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export const GLFeatureName = {
99
'WEBGL_compressed_texture_s3tc': 'texture-compression-bc',
1010
'EXT_texture_compression_bptc': 'texture-compression-bptc',
1111
'EXT_disjoint_timer_query_webgl2': 'timestamp-query',
12+
'OVR_multiview2': 'OVR_multiview2'
1213

1314
};

0 commit comments

Comments
 (0)