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 );