diff --git a/examples/files.json b/examples/files.json index b5e001af515a8b..7bdc29100ec131 100644 --- a/examples/files.json +++ b/examples/files.json @@ -355,7 +355,8 @@ "webxr_vr_rollercoaster", "webxr_vr_sandbox", "webxr_vr_sculpt", - "webxr_vr_video" + "webxr_vr_video", + "webxr_vr_layers" ], "games": [ "games_fps" diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index f40ed854865d53..20687329b6f110 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -68,7 +68,7 @@ class VRButton { // ('local' is always available for immersive sessions and doesn't need to // be requested separately.) - const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking' ] }; + const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); } else { diff --git a/examples/screenshots/webxr_vr_layers.jpg b/examples/screenshots/webxr_vr_layers.jpg new file mode 100644 index 00000000000000..001fe203e0b94e Binary files /dev/null and b/examples/screenshots/webxr_vr_layers.jpg differ diff --git a/examples/webxr_vr_layers.html b/examples/webxr_vr_layers.html new file mode 100644 index 00000000000000..dc8686ff404bc0 --- /dev/null +++ b/examples/webxr_vr_layers.html @@ -0,0 +1,171 @@ + + + + three.js vr - layers + + + + + + +
+ three.js media and projection layers
+ (Oculus Browser with #webxr-hands and #webxr-layers flags enabled) +
+ + + + diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index f56fd33a0b9d1e..54f12ca32d780a 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -16,13 +16,15 @@ class WebXRManager extends EventDispatcher { const state = renderer.state; let session = null; - let framebufferScaleFactor = 1.0; let referenceSpace = null; let referenceSpaceType = 'local-floor'; let pose = null; + let glBinding = null; + let glFramebuffer = null; + let glProjLayer = null; const controllers = []; const inputSourcesMap = new Map(); @@ -199,18 +201,47 @@ class WebXRManager extends EventDispatcher { } - const layerInit = { - antialias: attributes.antialias, - alpha: attributes.alpha, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; + if ( session.renderState.layers === undefined ) { + + const layerInit = { + antialias: attributes.antialias, + alpha: attributes.alpha, + depth: attributes.depth, + stencil: attributes.stencil, + framebufferScaleFactor: framebufferScaleFactor + }; + + // eslint-disable-next-line no-undef + const baseLayer = new XRWebGLLayer( session, gl, layerInit ); + + session.updateRenderState( { baseLayer: baseLayer } ); + + } else { + + let depthFormat = 0; + + if ( attributes.depth ) { + + depthFormat = attributes.stencil ? gl.DEPTH_STENCIL : gl.DEPTH_COMPONENT; + + } + + const projectionlayerInit = { + colorFormat: attributes.alpha ? gl.RGBA : gl.RGB, + depthFormat: depthFormat, + scaleFactor: framebufferScaleFactor + }; + + // eslint-disable-next-line no-undef + glBinding = new XRWebGLBinding( session, gl ); + + glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - // eslint-disable-next-line no-undef - const baseLayer = new XRWebGLLayer( session, gl, layerInit ); + glFramebuffer = gl.createFramebuffer(); - session.updateRenderState( { baseLayer: baseLayer } ); + session.updateRenderState( { layers: [ glProjLayer ] } ); + + } referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); @@ -429,9 +460,14 @@ class WebXRManager extends EventDispatcher { if ( pose !== null ) { const views = pose.views; + const baseLayer = session.renderState.baseLayer; - state.bindXRFramebuffer( baseLayer.framebuffer ); + if ( session.renderState.layers === undefined ) { + + state.bindXRFramebuffer( baseLayer.framebuffer ); + + } let cameraVRNeedsUpdate = false; @@ -440,18 +476,50 @@ class WebXRManager extends EventDispatcher { if ( views.length !== cameraVR.cameras.length ) { cameraVR.cameras.length = 0; + cameraVRNeedsUpdate = true; + } for ( let i = 0; i < views.length; i ++ ) { const view = views[ i ]; - const viewport = baseLayer.getViewport( view ); + + let viewport = null; + + if ( session.renderState.layers === undefined ) { + + viewport = baseLayer.getViewport( view ); + + } else { + + const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); + + gl.bindFramebuffer( gl.FRAMEBUFFER, glFramebuffer ); + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glSubImage.colorTexture, 0 ); + + if ( glSubImage.depthStencilTexture !== undefined ) { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, glSubImage.depthStencilTexture, 0 ); + + } + + gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + + state.bindXRFramebuffer( glFramebuffer ); + + viewport = glSubImage.viewport; + + } const camera = cameras[ i ]; + camera.matrix.fromArray( view.transform.matrix ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); if ( i === 0 ) {