diff --git a/examples/webxr_xr_ballshooter.html b/examples/webxr_xr_ballshooter.html index 28e39f2df18181..7a205137f809f1 100644 --- a/examples/webxr_xr_ballshooter.html +++ b/examples/webxr_xr_ballshooter.html @@ -67,7 +67,7 @@ // - renderer = new THREE.WebGLRenderer( { antialias: true } ); + renderer = new THREE.WebGLRenderer( { antialias: true, multiviewStereo: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( render ); diff --git a/src/loaders/ObjectLoader.js b/src/loaders/ObjectLoader.js index d430386baac682..3d7efc01a8a8b0 100644 --- a/src/loaders/ObjectLoader.js +++ b/src/loaders/ObjectLoader.js @@ -282,6 +282,21 @@ class ObjectLoader extends Loader { geometry = bufferGeometryLoader.parse( data ); break; + case 'Geometry': + + if ( 'THREE' in window && 'LegacyJSONLoader' in THREE ) { + + var geometryLoader = new THREE.LegacyJSONLoader(); + geometry = geometryLoader.parse( data, this.resourcePath ).geometry; + + + } else { + + console.error( 'THREE.ObjectLoader: You have to import LegacyJSONLoader in order load geometry data of type "Geometry".' ); + + } + break; + default: if ( data.type in Geometries ) { diff --git a/src/renderers/WebGLMultiviewRenderTarget.js b/src/renderers/WebGLMultiviewRenderTarget.js new file mode 100644 index 00000000000000..016d40ecf09ddd --- /dev/null +++ b/src/renderers/WebGLMultiviewRenderTarget.js @@ -0,0 +1,35 @@ +/** + * @author fernandojsg / http://fernandojsg.com + * @author Takahiro https://github.com/takahirox + */ + +import { WebGLRenderTarget } from './WebGLRenderTarget.js'; + +class WebGLMultiviewRenderTarget extends WebGLRenderTarget { + + constructor( width, height, numViews, options = {} ) { + + super( width, height, options ); + + this.depthBuffer = false; + this.stencilBuffer = false; + + this.numViews = numViews; + + } + + copy( source ) { + + super.copy( source ); + + this.numViews = source.numViews; + + return this; + + } + +} + +WebGLMultiviewRenderTarget.prototype.isWebGLMultiviewRenderTarget = true; + +export { WebGLMultiviewRenderTarget }; diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 901af91dbaac4c..8a2b79e0dec327 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -43,6 +43,7 @@ import { WebGLGeometries } from './webgl/WebGLGeometries.js'; import { WebGLIndexedBufferRenderer } from './webgl/WebGLIndexedBufferRenderer.js'; import { WebGLInfo } from './webgl/WebGLInfo.js'; import { WebGLMorphtargets } from './webgl/WebGLMorphtargets.js'; +import { WebGLMultiview } from './webgl/WebGLMultiview.js'; import { WebGLObjects } from './webgl/WebGLObjects.js'; import { WebGLPrograms } from './webgl/WebGLPrograms.js'; import { WebGLProperties } from './webgl/WebGLProperties.js'; @@ -54,6 +55,7 @@ import { WebGLState } from './webgl/WebGLState.js'; import { WebGLTextures } from './webgl/WebGLTextures.js'; import { WebGLUniforms } from './webgl/WebGLUniforms.js'; import { WebGLUtils } from './webgl/WebGLUtils.js'; +import { WebVRManager } from './webvr/WebVRManager.js'; import { WebXRManager } from './webxr/WebXRManager.js'; import { WebGLMaterials } from './webgl/WebGLMaterials.js'; import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js'; @@ -75,6 +77,7 @@ class WebGLRenderer { preserveDrawingBuffer = false, powerPreference = 'default', failIfMajorPerformanceCaveat = false, + multiviewStereo = false, } = parameters; this.isWebGLRenderer = true; @@ -282,6 +285,7 @@ class WebGLRenderer { let extensions, capabilities, state, info; let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; let programCache, materials, renderLists, renderStates, clipping, shadowMap; + let multiview; let background, morphtargets, bufferRenderer, indexedBufferRenderer; @@ -314,6 +318,7 @@ class WebGLRenderer { renderLists = new WebGLRenderLists(); renderStates = new WebGLRenderStates( extensions ); background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); + multiview = new WebGLMultiview( _this, extensions, _gl ); shadowMap = new WebGLShadowMap( _this, objects, capabilities ); uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); @@ -336,7 +341,7 @@ class WebGLRenderer { // xr - const xr = new WebXRManager( _this, _gl ); + const xr = ( typeof navigator !== 'undefined' && 'xr' in navigator ) ? new WebXRManager( _this, _gl, extensions, multiviewStereo ) : new WebVRManager( _this ); this.xr = xr; @@ -1183,13 +1188,23 @@ class WebGLRenderer { if ( camera.isArrayCamera ) { - const cameras = camera.cameras; + if ( xr.enabled && xr.isMultiview ) { - for ( let i = 0, l = cameras.length; i < l; i ++ ) { + textures.setDeferTextureUploads( true ); - const camera2 = cameras[ i ]; + renderScene( currentRenderList, scene, camera, camera.cameras[ 0 ].viewport ); - renderScene( currentRenderList, scene, camera2, camera2.viewport ); + } else { + + const cameras = camera.cameras; + + for ( let i = 0, l = cameras.length; i < l; i ++ ) { + + const camera2 = cameras[ i ]; + + renderScene( currentRenderList, scene, camera2, camera2.viewport ); + + } } @@ -1217,6 +1232,13 @@ class WebGLRenderer { if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); + textures.runDeferredUploads(); + + if ( xr.enabled && xr.submitFrame ) { + + xr.submitFrame(); + + } // _gl.finish(); bindingStates.resetDefaultState(); @@ -1695,6 +1717,7 @@ class WebGLRenderer { materialProperties.vertexAlphas = parameters.vertexAlphas; materialProperties.vertexTangents = parameters.vertexTangents; materialProperties.toneMapping = parameters.toneMapping; + materialProperties.numMultiviewViews = parameters.numMultiviewViews; } @@ -1726,6 +1749,8 @@ class WebGLRenderer { } + const numMultiviewViews = _currentRenderTarget && _currentRenderTarget.isWebGLMultiviewRenderTarget ? _currentRenderTarget.numViews : 0; + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; @@ -1845,6 +1870,10 @@ class WebGLRenderer { needsProgramChange = true; + } else if ( materialProperties.numMultiviewViews !== numMultiviewViews ) { + + needsProgramChange = true; + } } else { @@ -1891,8 +1920,17 @@ class WebGLRenderer { // common camera uniforms - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + if ( program.numMultiviewViews > 0 ) { + + multiview.updateCameraProjectionMatricesUniform( camera, p_uniforms ); + multiview.updateCameraViewMatricesUniform( camera, p_uniforms ); + + } else { + + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + + } const uCamPos = p_uniforms.map.cameraPosition; @@ -2044,8 +2082,17 @@ class WebGLRenderer { // common matrices - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + if ( program.numMultiviewViews > 0 ) { + + multiview.updateObjectMatricesUniforms( object, camera, p_uniforms ); + + } else { + + p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); + p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + + } + p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); // UBOs @@ -2095,6 +2142,32 @@ class WebGLRenderer { } + this.setTexture2D = ( function () { + + var warned = false; + + // backwards compatibility: peel texture.texture + return function setTexture2D( texture, slot ) { + + if ( texture && texture.isWebGLRenderTarget ) { + + if ( ! warned ) { + + console.warn( "THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead." ); + warned = true; + + } + + texture = texture.texture; + + } + + textures.setTexture2D( texture, slot ); + + }; + + }() ); + this.getActiveCubeFace = function () { return _currentActiveCubeFace; @@ -2123,7 +2196,7 @@ class WebGLRenderer { renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { + if ( ! renderTargetProperties.__autoAllocateDepthBuffer && ( ! _currentRenderTarget || ! _currentRenderTarget.isWebGLMultiviewRenderTarget ) ) { // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. diff --git a/src/renderers/webgl/WebGLBackground.js b/src/renderers/webgl/WebGLBackground.js index 03dd387e54db12..f7fc66fb748a08 100644 --- a/src/renderers/webgl/WebGLBackground.js +++ b/src/renderers/webgl/WebGLBackground.js @@ -72,7 +72,7 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, if ( boxMesh === undefined ) { boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), + new BoxGeometry( 10000, 10000, 10000 ), new ShaderMaterial( { name: 'BackgroundCubeMaterial', uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), diff --git a/src/renderers/webgl/WebGLMultiview.js b/src/renderers/webgl/WebGLMultiview.js new file mode 100644 index 00000000000000..4e7d581012e39f --- /dev/null +++ b/src/renderers/webgl/WebGLMultiview.js @@ -0,0 +1,100 @@ +/** + * @author fernandojsg / http://fernandojsg.com + * @author Takahiro https://github.com/takahirox + */ +import { Matrix3 } from '../../math/Matrix3.js'; +import { Matrix4 } from '../../math/Matrix4.js'; + +class WebGLMultiview { + + constructor( renderer, extensions, gl ) { + + this.renderer = renderer; + + this.DEFAULT_NUMVIEWS = 2; + this.maxNumViews = 0; + this.gl = gl; + + this.extensions = extensions; + + this.available = this.extensions.has( 'OCULUS_multiview' ); + + if ( this.available ) { + + const extension = this.extensions.get( 'OCULUS_multiview' ); + + this.maxNumViews = this.gl.getParameter( extension.MAX_VIEWS_OVR ); + + this.mat4 = []; + this.mat3 = []; + this.cameraArray = []; + + for ( var i = 0; i < this.maxNumViews; i ++ ) { + + this.mat4[ i ] = new Matrix4(); + this.mat3[ i ] = new Matrix3(); + + } + + } + + } + + // + getCameraArray( camera ) { + + if ( camera.isArrayCamera ) return camera.cameras; + + this.cameraArray[ 0 ] = camera; + + return this.cameraArray; + + } + + updateCameraProjectionMatricesUniform( camera, uniforms ) { + + var cameras = this.getCameraArray( camera ); + + for ( var i = 0; i < cameras.length; i ++ ) { + + this.mat4[ i ].copy( cameras[ i ].projectionMatrix ); + + } + + uniforms.setValue( this.gl, 'projectionMatrices', this.mat4 ); + + } + + updateCameraViewMatricesUniform( camera, uniforms ) { + + var cameras = this.getCameraArray( camera ); + + for ( var i = 0; i < cameras.length; i ++ ) { + + this.mat4[ i ].copy( cameras[ i ].matrixWorldInverse ); + + } + + uniforms.setValue( this.gl, 'viewMatrices', this.mat4 ); + + } + + updateObjectMatricesUniforms( object, camera, uniforms ) { + + var cameras = this.getCameraArray( camera ); + + for ( var i = 0; i < cameras.length; i ++ ) { + + this.mat4[ i ].multiplyMatrices( cameras[ i ].matrixWorldInverse, object.matrixWorld ); + this.mat3[ i ].getNormalMatrix( this.mat4[ i ] ); + + } + + uniforms.setValue( this.gl, 'modelViewMatrices', this.mat4 ); + uniforms.setValue( this.gl, 'normalMatrices', this.mat3 ); + + } + +} + +export { WebGLMultiview }; diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index 2f948ff8f2db36..cfd15ace1f99d1 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -481,6 +481,8 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { let prefixVertex, prefixFragment; let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; + const numMultiviewViews = parameters.numMultiviewViews; + if ( parameters.isRawShaderMaterial ) { prefixVertex = [ @@ -891,6 +893,53 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { '#define textureCubeGradEXT textureGrad' ].join( '\n' ) + '\n' + prefixFragment; + // Multiview + + if ( numMultiviewViews > 0 ) { + + // TODO: fix light transforms here? + + prefixVertex = [ + '#extension GL_OVR_multiview : require', + 'layout(num_views = ' + numMultiviewViews + ') in;', + '#define VIEW_ID gl_ViewID_OVR' + ].join( '\n' ) + '\n' + prefixVertex; + + prefixVertex = prefixVertex.replace( + [ + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;' + ].join( '\n' ), + [ + 'uniform mat4 modelViewMatrices[' + numMultiviewViews + '];', + 'uniform mat4 projectionMatrices[' + numMultiviewViews + '];', + 'uniform mat4 viewMatrices[' + numMultiviewViews + '];', + 'uniform mat3 normalMatrices[' + numMultiviewViews + '];', + + '#define modelViewMatrix modelViewMatrices[VIEW_ID]', + '#define projectionMatrix projectionMatrices[VIEW_ID]', + '#define viewMatrix viewMatrices[VIEW_ID]', + '#define normalMatrix normalMatrices[VIEW_ID]' + ].join( '\n' ) + ); + + prefixFragment = [ + '#extension GL_OVR_multiview : require', + '#define VIEW_ID gl_ViewID_OVR' + ].join( '\n' ) + '\n' + prefixFragment; + + prefixFragment = prefixFragment.replace( + 'uniform mat4 viewMatrix;', + [ + 'uniform mat4 viewMatrices[' + numMultiviewViews + '];', + '#define viewMatrix viewMatrices[VIEW_ID]' + ].join( '\n' ) + ); + + } + } const vertexGlsl = versionString + prefixVertex + vertexShader; @@ -1083,6 +1132,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { this.program = program; this.vertexShader = glVertexShader; this.fragmentShader = glFragmentShader; + this.numMultiviewViews = numMultiviewViews; return this; diff --git a/src/renderers/webgl/WebGLPrograms.js b/src/renderers/webgl/WebGLPrograms.js index 4b0f07f7028b68..b2c97442112095 100644 --- a/src/renderers/webgl/WebGLPrograms.js +++ b/src/renderers/webgl/WebGLPrograms.js @@ -109,6 +109,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities const currentRenderTarget = renderer.getRenderTarget(); + const numMultiviewViews = currentRenderTarget && currentRenderTarget.isWebGLMultiviewRenderTarget ? currentRenderTarget.numViews : 0; + const IS_INSTANCEDMESH = object.isInstancedMesh === true; const IS_BATCHEDMESH = object.isBatchedMesh === true; @@ -196,6 +198,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, + numMultiviewViews: numMultiviewViews, outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), alphaToCoverage: !! material.alphaToCoverage, @@ -551,6 +554,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities _programLayers.enable( 19 ); if ( parameters.alphaToCoverage ) _programLayers.enable( 20 ); + if ( parameters.numMultiviewViews ) + _programLayers.enable( 21 ); array.push( _programLayers.mask ); diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 4f2cbe0d03c7ad..45271916a52c48 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -7,6 +7,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); + const multiviewExt = extensions.has( 'OCULUS_multiview' ) ? extensions.get( 'OCULUS_multiview' ) : null; const _imageDimensions = new Vector2(); const _videoTextures = new WeakMap(); @@ -14,6 +15,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source + let _deferredUploads = []; + let _deferTextureUploads = false; + // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). @@ -453,8 +457,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else { - uploadTexture( textureProperties, texture, slot ); - return; + if ( uploadTexture( textureProperties, texture, slot ) ) { + + return; + + } } @@ -663,8 +670,45 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + function setDeferTextureUploads( deferFlag ) { + + _deferTextureUploads = deferFlag; + + } + + function runDeferredUploads() { + + const previousDeferSetting = _deferTextureUploads; + _deferTextureUploads = false; + + for ( const upload of _deferredUploads ) { + + uploadTexture( upload.textureProperties, upload.texture, upload.slot ); + upload.texture.isPendingDeferredUpload = false; + + } + + _deferredUploads = []; + + _deferTextureUploads = previousDeferSetting; + + } + function uploadTexture( textureProperties, texture, slot ) { + if ( _deferTextureUploads ) { + + if ( ! texture.isPendingDeferredUpload ) { + + texture.isPendingDeferredUpload = true; + _deferredUploads.push( { textureProperties: textureProperties, texture: texture, slot: slot } ); + + } + + return false; + + } + let textureType = _gl.TEXTURE_2D; if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; @@ -1070,6 +1114,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } textureProperties.__version = texture.version; + return true; } @@ -1325,7 +1370,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const width = Math.max( 1, renderTarget.width >> level ); const height = Math.max( 1, renderTarget.height >> level ); - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { + if ( renderTarget.isWebGLMultiviewRenderTarget === true ) { + + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.numViews ); + + } else if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); @@ -1339,13 +1388,31 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - if ( useMultisampledRTT( renderTarget ) ) { + const multisampled = useMultisampledRTT( renderTarget ); + + if ( renderTarget.isWebGLMultiviewRenderTarget === true ) { - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + if ( multisampled ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ), 0, renderTarget.numViews ); + + } else { + + multiviewExt.framebufferTextureMultiviewOVR( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( texture ).__webglTexture, 0, 0, renderTarget.numViews ); + + } } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level ); + if ( multisampled ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level ); + + } } @@ -1359,7 +1426,59 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + if ( renderTarget.isWebGLMultiviewRenderTarget === true ) { + + const useMultisample = useMultisampledRTT( renderTarget ); + const numViews = renderTarget.numViews; + + const depthTexture = renderTarget.depthTexture; + let glInternalFormat = _gl.DEPTH_COMPONENT24; + let glDepthAttachment = _gl.DEPTH_ATTACHMENT; + + if ( depthTexture && depthTexture.isDepthTexture ) { + + if ( depthTexture.type === FloatType ) { + + glInternalFormat = _gl.DEPTH_COMPONENT32F; + + } else if ( depthTexture.type === UnsignedInt248Type ) { + + glInternalFormat = _gl.DEPTH24_STENCIL8; + glDepthAttachment = _gl.DEPTH_STENCIL_ATTACHMENT; + + } + + // we're defaulting to _gl.DEPTH_COMPONENT24 so don't assign here + // or else DeepScan will complain + + // else if ( depthTexture.type === UnsignedIntType ) { + + // glInternalFormat = _gl.DEPTH_COMPONENT24; + + // } + + } + + let depthStencilTexture = properties.get( renderTarget.depthTexture ).__webglTexture; + if ( depthStencilTexture === undefined ) { + + depthStencilTexture = _gl.createTexture(); + _gl.bindTexture( _gl.TEXTURE_2D_ARRAY, depthStencilTexture ); + _gl.texStorage3D( _gl.TEXTURE_2D_ARRAY, 1, glInternalFormat, renderTarget.width, renderTarget.height, numViews ); + + } + + if ( useMultisample ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( _gl.FRAMEBUFFER, glDepthAttachment, depthStencilTexture, 0, getRenderTargetSamples( renderTarget ), 0, numViews ); + + } else { + + multiviewExt.framebufferTextureMultiviewOVR( _gl.FRAMEBUFFER, glDepthAttachment, depthStencilTexture, 0, 0, numViews ); + + } + + } else if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { let glInternalFormat = _gl.DEPTH_COMPONENT24; @@ -1483,37 +1602,85 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } setTexture2D( renderTarget.depthTexture, 0 ); + if ( renderTarget.depthTexture.image.depth != 1 ) { + + setTexture2DArray( renderTarget.depthTexture, 0 ); + + } else { + + setTexture2D( renderTarget.depthTexture, 0 ); + + } const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; const samples = getRenderTargetSamples( renderTarget ); - if ( renderTarget.depthTexture.format === DepthFormat ) { + if ( renderTarget.isWebGLMultiviewRenderTarget === true ) { - if ( useMultisampledRTT( renderTarget ) ) { + const useMultisample = useMultisampledRTT( renderTarget ); + const numViews = renderTarget.numViews; - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + if ( renderTarget.depthTexture.format === DepthFormat ) { - } else { + if ( useMultisample ) { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + multiviewExt.framebufferTextureMultisampleMultiviewOVR( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, webglDepthTexture, 0, samples, 0, numViews ); - } + } else { - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { + multiviewExt.framebufferTextureMultiviewOVR( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, webglDepthTexture, 0, 0, numViews ); - if ( useMultisampledRTT( renderTarget ) ) { + } + + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + if ( useMultisample ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, webglDepthTexture, 0, samples, 0, numViews ); + + } else { + + multiviewExt.framebufferTextureMultiviewOVR( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, webglDepthTexture, 0, 0, numViews ); + + } } else { - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + throw new Error( 'Unknown depthTexture format' ); } } else { - throw new Error( 'Unknown depthTexture format' ); + if ( renderTarget.depthTexture.format === DepthFormat ) { + + if ( useMultisampledRTT( renderTarget ) ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } + + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { + + if ( useMultisampledRTT( renderTarget ) ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + + } else { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } + + } else { + + throw new Error( 'Unknown depthTexture format' ); + + } } @@ -1771,6 +1938,12 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + if ( renderTarget.isWebGLMultiviewRenderTarget === true ) { + + glTextureType = _gl.TEXTURE_2D_ARRAY; + + } + state.bindTexture( glTextureType, textureProperties.__webglTexture ); setTextureParameters( glTextureType, texture ); @@ -1800,9 +1973,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // Setup depth and stencil buffers - if ( renderTarget.depthBuffer ) { + if ( renderTarget.depthBuffer || renderTarget.isWebGLMultiviewRenderTarget === true ) { - setupDepthRenderbuffer( renderTarget ); + this.setupDepthRenderbuffer( renderTarget ); } @@ -2039,12 +2212,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, this.setTexture3D = setTexture3D; this.setTextureCube = setTextureCube; this.rebindTextures = rebindTextures; + this.uploadTexture = uploadTexture; this.setupRenderTarget = setupRenderTarget; this.updateRenderTargetMipmap = updateRenderTargetMipmap; this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; + this.setupDepthTexture = setupDepthTexture; this.setupDepthRenderbuffer = setupDepthRenderbuffer; this.setupFrameBufferTexture = setupFrameBufferTexture; this.useMultisampledRTT = useMultisampledRTT; + this.runDeferredUploads = runDeferredUploads; + this.setDeferTextureUploads = setDeferTextureUploads; } diff --git a/src/renderers/webvr/WebVRManager.js b/src/renderers/webvr/WebVRManager.js new file mode 100644 index 00000000000000..06a0a79b846f21 --- /dev/null +++ b/src/renderers/webvr/WebVRManager.js @@ -0,0 +1,495 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +import { EventDispatcher } from '../../core/EventDispatcher.js'; +import { Group } from '../../objects/Group.js'; +import { Matrix4 } from '../../math/Matrix4.js'; +import { Vector2 } from '../../math/Vector2.js'; +import { Vector3 } from '../../math/Vector3.js'; +import { Vector4 } from '../../math/Vector4.js'; +import { Quaternion } from '../../math/Quaternion.js'; +import { ArrayCamera } from '../../cameras/ArrayCamera.js'; +import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js'; +import { WebGLAnimation } from '../webgl/WebGLAnimation.js'; +import { setProjectionFromUnion } from './WebVRUtils.js'; + +function WebVRManager( renderer ) { + + var renderWidth, renderHeight; + var scope = this; + + var device = null; + var frameData = null; + + var poseTarget = null; + + var controllers = []; + var standingMatrix = new Matrix4(); + var standingMatrixInverse = new Matrix4(); + + var framebufferScaleFactor = 1.0; + + var referenceSpaceType = 'local-floor'; + + if ( typeof window !== 'undefined' && 'VRFrameData' in window ) { + + frameData = new window.VRFrameData(); + window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false ); + + } + + var matrixWorldInverse = new Matrix4(); + var tempQuaternion = new Quaternion(); + var tempPosition = new Vector3(); + + var cameraL = new PerspectiveCamera(); + cameraL.viewport = new Vector4(); + cameraL.layers.enable( 1 ); + + var cameraR = new PerspectiveCamera(); + cameraR.viewport = new Vector4(); + cameraR.layers.enable( 2 ); + + var cameraVR = new ArrayCamera( [ cameraL, cameraR ] ); + cameraVR.layers.enable( 1 ); + cameraVR.layers.enable( 2 ); + + var currentSize = new Vector2(), currentPixelRatio; + + function onVRDisplayPresentChange() { + + var isPresenting = scope.isPresenting = device !== null && device.isPresenting === true; + + if ( isPresenting ) { + + var eyeParameters = device.getEyeParameters( 'left' ); + renderWidth = 2 * eyeParameters.renderWidth * framebufferScaleFactor; + renderHeight = eyeParameters.renderHeight * framebufferScaleFactor; + + currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( currentSize ); + + renderer.setDrawingBufferSize( renderWidth, renderHeight, 1 ); + + cameraL.viewport.set( 0, 0, renderWidth / 2, renderHeight ); + cameraR.viewport.set( renderWidth / 2, 0, renderWidth / 2, renderHeight ); + + animation.start(); + + scope.dispatchEvent( { type: 'sessionstart' } ); + + } else { + + if ( scope.enabled ) { + + renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio ); + + } + + animation.stop(); + + scope.dispatchEvent( { type: 'sessionend' } ); + + } + + } + + // + + var triggers = []; + var grips = []; + + function findGamepad( id ) { + + var gamepads = navigator.getGamepads && navigator.getGamepads(); + + for ( var i = 0, l = gamepads.length; i < l; i ++ ) { + + var gamepad = gamepads[ i ]; + + if ( gamepad && ( gamepad.id === 'Daydream Controller' || + gamepad.id === 'Gear VR Controller' || gamepad.id === 'Oculus Go Controller' || + gamepad.id === 'OpenVR Gamepad' || gamepad.id.startsWith( 'Oculus Touch' ) || + gamepad.id.startsWith( 'HTC Vive Focus' ) || + gamepad.id.startsWith( 'Spatial Controller' ) ) ) { + + var hand = gamepad.hand; + + if ( id === 0 && ( hand === '' || hand === 'right' ) ) return gamepad; + if ( id === 1 && ( hand === 'left' ) ) return gamepad; + + } + + } + + } + + function updateControllers() { + + for ( var i = 0; i < controllers.length; i ++ ) { + + var controller = controllers[ i ]; + + var gamepad = findGamepad( i ); + + if ( gamepad !== undefined && gamepad.pose !== undefined ) { + + if ( gamepad.pose === null ) return; + + // Pose + + var pose = gamepad.pose; + + if ( pose.hasPosition === false ) controller.position.set( 0.2, - 0.6, - 0.05 ); + + if ( pose.position !== null ) controller.position.fromArray( pose.position ); + if ( pose.orientation !== null ) controller.quaternion.fromArray( pose.orientation ); + controller.matrix.compose( controller.position, controller.quaternion, controller.scale ); + controller.matrix.premultiply( standingMatrix ); + controller.matrix.decompose( controller.position, controller.quaternion, controller.scale ); + controller.matrixWorldNeedsUpdate = true; + controller.visible = true; + + // Trigger + + var buttonId = gamepad.id === 'Daydream Controller' ? 0 : 1; + + if ( triggers[ i ] === undefined ) triggers[ i ] = false; + + if ( triggers[ i ] !== gamepad.buttons[ buttonId ].pressed ) { + + triggers[ i ] = gamepad.buttons[ buttonId ].pressed; + + if ( triggers[ i ] === true ) { + + controller.dispatchEvent( { type: 'selectstart' } ); + + } else { + + controller.dispatchEvent( { type: 'selectend' } ); + controller.dispatchEvent( { type: 'select' } ); + + } + + } + + // Grip + buttonId = 2; + + if ( grips[ i ] === undefined ) grips[ i ] = false; + + // Skip if the grip button doesn't exist on this controller + if ( gamepad.buttons[ buttonId ] !== undefined ) { + + if ( grips[ i ] !== gamepad.buttons[ buttonId ].pressed ) { + + grips[ i ] = gamepad.buttons[ buttonId ].pressed; + + if ( grips[ i ] === true ) { + + controller.dispatchEvent( { type: 'squeezestart' } ); + + } else { + + controller.dispatchEvent( { type: 'squeezeend' } ); + controller.dispatchEvent( { type: 'squeeze' } ); + + } + + } + + } + + } else { + + controller.visible = false; + + } + + } + + } + + function updateViewportFromBounds( viewport, bounds ) { + + if ( bounds !== null && bounds.length === 4 ) { + + viewport.set( bounds[ 0 ] * renderWidth, bounds[ 1 ] * renderHeight, bounds[ 2 ] * renderWidth, bounds[ 3 ] * renderHeight ); + + } + + } + + // + + this.enabled = false; + + this.getController = function ( id ) { + + var controller = controllers[ id ]; + + if ( controller === undefined ) { + + controller = new Group(); + controller.matrixAutoUpdate = false; + controller.visible = false; + + controllers[ id ] = controller; + + } + + return controller; + + }; + + this.getDevice = function () { + + return device; + + }; + + this.setDevice = function ( value ) { + + if ( value !== undefined ) device = value; + + animation.setContext( value ); + + }; + + this.setFramebufferScaleFactor = function ( value ) { + + framebufferScaleFactor = value; + + }; + + this.setReferenceSpaceType = function ( value ) { + + referenceSpaceType = value; + + }; + + this.setPoseTarget = function ( object ) { + + if ( object !== undefined ) poseTarget = object; + + }; + + // + + this.cameraAutoUpdate = true; + + this.updateCamera = function ( camera ) { + + var userHeight = referenceSpaceType === 'local-floor' ? 1.6 : 0; + + device.depthNear = camera.near; + device.depthFar = camera.far; + + device.getFrameData( frameData ); + + // + + if ( referenceSpaceType === 'local-floor' ) { + + var stageParameters = device.stageParameters; + + if ( stageParameters ) { + + standingMatrix.fromArray( stageParameters.sittingToStandingTransform ); + + } else { + + standingMatrix.makeTranslation( 0, userHeight, 0 ); + + } + + } + + + var pose = frameData.pose; + var poseObject = poseTarget !== null ? poseTarget : camera; + + // We want to manipulate poseObject by its position and quaternion components since users may rely on them. + poseObject.matrix.copy( standingMatrix ); + poseObject.matrix.decompose( poseObject.position, poseObject.quaternion, poseObject.scale ); + + if ( pose.orientation !== null ) { + + tempQuaternion.fromArray( pose.orientation ); + poseObject.quaternion.multiply( tempQuaternion ); + + } + + if ( pose.position !== null ) { + + tempQuaternion.setFromRotationMatrix( standingMatrix ); + tempPosition.fromArray( pose.position ); + tempPosition.applyQuaternion( tempQuaternion ); + poseObject.position.add( tempPosition ); + + } + + poseObject.updateMatrixWorld(); + + var children = poseObject.children; + for ( var i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].updateMatrixWorld( true ); + + } + + // + + cameraL.near = camera.near; + cameraR.near = camera.near; + + cameraL.far = camera.far; + cameraR.far = camera.far; + + cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix ); + cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix ); + + // TODO (mrdoob) Double check this code + + standingMatrixInverse.copy( standingMatrix ).invert(); + + if ( referenceSpaceType === 'local-floor' ) { + + cameraL.matrixWorldInverse.multiply( standingMatrixInverse ); + cameraR.matrixWorldInverse.multiply( standingMatrixInverse ); + + } + + var parent = poseObject.parent; + + if ( parent !== null ) { + + matrixWorldInverse.copy( parent.matrixWorld ).invert(); + + cameraL.matrixWorldInverse.multiply( matrixWorldInverse ); + cameraR.matrixWorldInverse.multiply( matrixWorldInverse ); + + } + + // envMap and Mirror needs camera.matrixWorld + + cameraL.matrixWorld.copy( cameraL.matrixWorldInverse ).invert(); + cameraR.matrixWorld.copy( cameraR.matrixWorldInverse ).invert(); + + cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix ); + cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix ); + + setProjectionFromUnion( cameraVR, cameraL, cameraR ); + + // + + var layers = device.getLayers(); + + if ( layers.length ) { + + var layer = layers[ 0 ]; + + updateViewportFromBounds( cameraL.viewport, layer.leftBounds ); + updateViewportFromBounds( cameraR.viewport, layer.rightBounds ); + + } + + updateControllers(); + + return cameraVR; + + }; + + this.getCamera = function () { + + return cameraVR; + + }; + + // Dummy getFoveation/setFoveation to have the same API as WebXR + + this.getFoveation = function () { + + return 1; + + }; + + this.setFoveation = function ( foveation ) { + + if ( foveation !== 1 ) { + + console.warn( 'THREE.WebVRManager: setFoveation() not used in WebVR.' ); + + } + + }; + + // Dummy getEnvironmentBlendMode to have the same API as WebXR + + this.getEnvironmentBlendMode = function () { + + if ( scope.isPresenting ) { + + return 'opaque'; + + } + + }; + + // + + this.getStandingMatrix = function () { + + return standingMatrix; + + }; + + this.isPresenting = false; + + // Animation Loop + + var animation = new WebGLAnimation(); + + this.setAnimationLoop = function ( callback ) { + + animation.setAnimationLoop( callback ); + + if ( this.isPresenting ) animation.start(); + + }; + + this.submitFrame = function () { + + if ( this.isPresenting ) device.submitFrame(); + + }; + + this.dispose = function () { + + if ( typeof window !== 'undefined' ) { + + window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange ); + + } + + }; + + // DEPRECATED + + this.setFrameOfReferenceType = function () { + + console.warn( 'THREE.WebVRManager: setFrameOfReferenceType() has been deprecated.' ); + + }; + +} + +Object.assign( WebVRManager.prototype, { + addEventListener: EventDispatcher.prototype.addEventListener, + hasEventListener: EventDispatcher.prototype.hasEventListener, + removeEventListener: EventDispatcher.prototype.removeEventListener, + dispatchEvent: EventDispatcher.prototype.dispatchEvent +} ); + +export { WebVRManager }; diff --git a/src/renderers/webvr/WebVRUtils.js b/src/renderers/webvr/WebVRUtils.js new file mode 100644 index 00000000000000..a9590fcfe2bb6c --- /dev/null +++ b/src/renderers/webvr/WebVRUtils.js @@ -0,0 +1,66 @@ +/** + * @author jsantell / https://www.jsantell.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +import { Vector3 } from '../../math/Vector3.js'; + +var cameraLPos = new Vector3(); +var cameraRPos = new Vector3(); + +/** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + */ +function setProjectionFromUnion( camera, cameraL, cameraR ) { + + cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); + + var ipd = cameraLPos.distanceTo( cameraRPos ); + + var projL = cameraL.projectionMatrix.elements; + var projR = cameraR.projectionMatrix.elements; + + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + var near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + var far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + var topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + var bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; + + var leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + var rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + var left = near * leftFov; + var right = near * rightFov; + + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + var zOffset = ipd / ( - leftFov + rightFov ); + var xOffset = zOffset * - leftFov; + + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + var near2 = near + zOffset; + var far2 = far + zOffset; + var left2 = left - xOffset; + var right2 = right + ( ipd - xOffset ); + var top2 = topFov * far / far2 * near2; + var bottom2 = bottomFov * far / far2 * near2; + + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + +} + +export { setProjectionFromUnion }; diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js index 56a6df92f03b04..4708e85e53d647 100644 --- a/src/renderers/webxr/WebXRManager.js +++ b/src/renderers/webxr/WebXRManager.js @@ -6,6 +6,7 @@ import { Vector3 } from '../../math/Vector3.js'; import { Vector4 } from '../../math/Vector4.js'; import { RAD2DEG } from '../../math/MathUtils.js'; import { WebGLAnimation } from '../webgl/WebGLAnimation.js'; +import { WebGLMultiviewRenderTarget } from '../WebGLMultiviewRenderTarget.js'; import { WebGLRenderTarget } from '../WebGLRenderTarget.js'; import { WebXRController } from './WebXRController.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; @@ -14,7 +15,7 @@ import { WebXRDepthSensing } from './WebXRDepthSensing.js'; class WebXRManager extends EventDispatcher { - constructor( renderer, gl ) { + constructor( renderer, gl, extensions, useMultiview ) { super(); @@ -23,6 +24,7 @@ class WebXRManager extends EventDispatcher { let session = null; let framebufferScaleFactor = 1.0; + var poseTarget = null; let referenceSpace = null; let referenceSpaceType = 'local-floor'; @@ -31,6 +33,8 @@ class WebXRManager extends EventDispatcher { let customReferenceSpace = null; let pose = null; + var layers = []; + let glBinding = null; let glProjLayer = null; let glBaseLayer = null; @@ -68,11 +72,18 @@ class WebXRManager extends EventDispatcher { let _currentDepthFar = null; // - this.cameraAutoUpdate = true; + this.layersEnabled = false; this.enabled = false; this.isPresenting = false; + this.isMultiview = false; + + this.getCameraPose = function ( ) { + + return pose; + + }; this.getController = function ( index ) { @@ -321,12 +332,20 @@ class WebXRManager extends EventDispatcher { } + scope.isMultiview = useMultiview && extensions.has( 'OCULUS_multiview' ); + const projectionlayerInit = { colorFormat: gl.RGBA8, depthFormat: glDepthFormat, scaleFactor: framebufferScaleFactor }; + if ( scope.isMultiview ) { + + projectionlayerInit.textureType = 'texture-array'; + + } + glBinding = new XRWebGLBinding( session, gl ); glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); @@ -336,17 +355,31 @@ class WebXRManager extends EventDispatcher { renderer.setPixelRatio( 1 ); renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 - } ); + const renderTargetOptions = { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + colorSpace: renderer.outputColorSpace, + samples: attributes.antialias ? 4 : 0 + }; + + if ( scope.isMultiview ) { + + const extension = extensions.get( 'OCULUS_multiview' ); + + this.maxNumViews = gl.getParameter( extension.MAX_VIEWS_OVR ); + + newRenderTarget = new WebGLMultiviewRenderTarget( glProjLayer.textureWidth, glProjLayer.textureHeight, 2, renderTargetOptions ); + + } else { + + newRenderTarget = new WebGLRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + renderTargetOptions ); + + } const renderTargetProperties = renderer.properties.get( newRenderTarget ); renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; @@ -378,8 +411,29 @@ class WebXRManager extends EventDispatcher { return session.environmentBlendMode; } + } - }; + this.addLayer = function(layer) { + if (!window.XRWebGLBinding || !this.layersEnabled || !session) { return; } + + layers.push( layer ); + this.updateLayers(); + } + + this.removeLayer = function(layer) { + + layers.splice( layers.indexOf(layer), 1 ); + if (!window.XRWebGLBinding || !this.layersEnabled || !session) { return; } + + this.updateLayers(); + } + + this.updateLayers = function() { + var layersCopy = layers.map(function (x) { return x; }); + + layersCopy.unshift( session.renderState.layers[0] ); + session.updateRenderState( { layers: layersCopy } ); + } function onInputSourcesChange( event ) { @@ -524,6 +578,12 @@ class WebXRManager extends EventDispatcher { } + this.setPoseTarget = function ( object ) { + + if ( object !== undefined ) poseTarget = object; + + }; + this.updateCamera = function ( camera ) { if ( session === null ) return; @@ -561,8 +621,9 @@ class WebXRManager extends EventDispatcher { } - const parent = camera.parent; const cameras = cameraXR.cameras; + var object = poseTarget || camera; + const parent = object.parent; updateCamera( cameraXR, parent ); @@ -586,28 +647,28 @@ class WebXRManager extends EventDispatcher { } - // update user camera and its children - - updateUserCamera( camera, cameraXR, parent ); + updateUserCamera( camera, cameraXR, object ); }; - function updateUserCamera( camera, cameraXR, parent ) { + function updateUserCamera( camera, cameraXR, object ) { - if ( parent === null ) { + cameraXR.matrixWorld.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); + + if ( object.parent === null ) { - camera.matrix.copy( cameraXR.matrixWorld ); + object.matrix.copy( cameraXR.matrixWorld ); } else { - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); + object.matrix.copy( object.parent.matrixWorld ); + object.matrix.invert(); + object.matrix.multiply( cameraXR.matrixWorld ); } - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + object.updateMatrixWorld( true ); camera.projectionMatrix.copy( cameraXR.projectionMatrix ); camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse );