From 3277fefbdfd5cd9053e71db12b1472dbb5671fc6 Mon Sep 17 00:00:00 2001 From: Fernando Serrano Date: Mon, 22 Apr 2019 22:18:52 +0200 Subject: [PATCH] Initial working implementation --- examples/files.js | 1 + examples/webvr_ballshooter_multiview.html | 277 ++++++++++++++++++++++ src/renderers/WebGLRenderer.js | 147 ++++++++++-- src/renderers/webgl/WebGLCapabilities.js | 11 +- src/renderers/webgl/WebGLMultiview.js | 105 ++++++++ src/renderers/webgl/WebGLProgram.js | 51 +++- 6 files changed, 564 insertions(+), 28 deletions(-) create mode 100644 examples/webvr_ballshooter_multiview.html create mode 100644 src/renderers/webgl/WebGLMultiview.js diff --git a/examples/files.js b/examples/files.js index 5af31eb9b3..561b28c07a 100644 --- a/examples/files.js +++ b/examples/files.js @@ -335,6 +335,7 @@ var files = { ], "webvr": [ "webvr_ballshooter", + "webvr_ballshooter_multiview", "webvr_cubes", "webvr_dragging", "webvr_lorenzattractor", diff --git a/examples/webvr_ballshooter_multiview.html b/examples/webvr_ballshooter_multiview.html new file mode 100644 index 0000000000..c0c7c8bb7b --- /dev/null +++ b/examples/webvr_ballshooter_multiview.html @@ -0,0 +1,277 @@ + + + + three.js webvr - ball shooter + + + + + + + + + + + + + + diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 2d15ed7f15..549fae9605 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -40,6 +40,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 { WebGLMultiview } from './webgl/WebGLMultiview.js'; import { WebVRManager } from './webvr/WebVRManager.js'; import { WebXRManager } from './webvr/WebXRManager.js'; @@ -58,6 +59,8 @@ function WebGLRenderer( parameters ) { var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ), _context = parameters.context !== undefined ? parameters.context : null, + _multiview = parameters.multiview !== undefined ? parameters.multiview : false, + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, _depth = parameters.depth !== undefined ? parameters.depth : true, _stencil = parameters.stencil !== undefined ? parameters.stencil : true, @@ -312,6 +315,20 @@ function WebGLRenderer( parameters ) { this.vr = vr; + + if ( _multiview && ! capabilities.multiview ) { + + console.warn( 'WebGLRenderer: Multiview requested but not supported by the browser' ); + this.vr.multiview = false; + + } else if ( _multiview !== false && capabilities.multiview ) { + + console.info( 'WebGLRenderer: Multiview enabled' ); + this.vr.multiview = true; + + } + + // shadow map var shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize ); @@ -1360,44 +1377,95 @@ function WebGLRenderer( parameters ) { } + var multiviewObject = new WebGLMultiview(_gl, _canvas, extensions ); + function renderObjects( renderList, scene, camera, overrideMaterial ) { - for ( var i = 0, l = renderList.length; i < l; i ++ ) { + if ( vr.multiview ) { + multiviewObject.bindMultiviewFrameBuffer(); + + _gl.disable( _gl.SCISSOR_TEST ); + + var width = _canvas.width; + var height = _canvas.height; + + var halfWidth = Math.floor(width * 0.5); + + _gl.viewport( 0, 0, halfWidth, height ); + renderer.setViewport( 0, 0, halfWidth, height ); + + _gl.clear( _gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT | _gl.STENCIL_BUFFER_BIT ); + + for ( var i = 0, l = renderList.length; i < l; i ++ ) { - var renderItem = renderList[ i ]; + var renderItem = renderList[ i ]; - var object = renderItem.object; - var geometry = renderItem.geometry; - var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; - var group = renderItem.group; + var object = renderItem.object; + var geometry = renderItem.geometry; + var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; + var group = renderItem.group; - if ( camera.isArrayCamera ) { + renderObject( object, scene, camera, geometry, material, group ); - _currentArrayCamera = camera; + } + + multiviewObject.unbindMultiviewFrameBuffer(); + + } else { + + for ( var i = 0, l = renderList.length; i < l; i ++ ) { + + var renderItem = renderList[ i ]; + + var object = renderItem.object; + var geometry = renderItem.geometry; + var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; + var group = renderItem.group; + + if ( camera.isArrayCamera ) { - var cameras = camera.cameras; + _currentArrayCamera = camera; - for ( var j = 0, jl = cameras.length; j < jl; j ++ ) { + var cameras = camera.cameras; - var camera2 = cameras[ j ]; + for ( var j = 0, jl = cameras.length; j < jl; j ++ ) { - if ( object.layers.test( camera2.layers ) ) { + var camera2 = cameras[ j ]; - state.viewport( _currentViewport.copy( camera2.viewport ) ); + if ( object.layers.test( camera2.layers ) ) { + + if ( 'viewport' in camera2 ) { // XR + + state.viewport( _currentViewport.copy( camera2.viewport ) ); + + } else { + + var bounds = camera2.bounds; + + var x = bounds.x * _width; + var y = bounds.y * _height; + var width = bounds.z * _width; + var height = bounds.w * _height; + + state.viewport( _currentViewport.set( x, y, width, height ).multiplyScalar( _pixelRatio ) ); + + } - currentRenderState.setupLights( camera2 ); + currentRenderState.setupLights( camera2 ); - renderObject( object, scene, camera2, geometry, material, group ); + renderObject( object, scene, camera2, geometry, material, group ); + + } } - } + } else { - } else { + _currentArrayCamera = null; - _currentArrayCamera = null; + renderObject( object, scene, camera, geometry, material, group ); - renderObject( object, scene, camera, geometry, material, group ); + } } @@ -1682,7 +1750,24 @@ function WebGLRenderer( parameters ) { if ( refreshProgram || _currentCamera !== camera ) { - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); + if ( vr.multiview ) { + + if ( false && vr.isPresenting() ) { + + // @todo Obviously remove the map :) + p_uniforms.setValue( _gl, 'projectionMatrices', camera.cameras.map( c => c.projectionMatrix ) ); + + } else { + + p_uniforms.setValue( _gl, 'projectionMatrices', [ camera.projectionMatrix, camera.projectionMatrix ] ); + + } + + } else { + + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); + + } if ( capabilities.logarithmicDepthBuffer ) { @@ -1730,7 +1815,27 @@ function WebGLRenderer( parameters ) { material.isShaderMaterial || material.skinning ) { - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + if ( vr.multiview ) { + + if ( vr.isPresenting() ) { + + // @todo Obviously remove the map :) + p_uniforms.setValue( _gl, 'viewMatrix', camera.cameras.map( c => c.matrixWorldInverse ) ); + + } else { + + var newMat = camera.matrixWorldInverse.clone(); + var newMat = new Matrix4(); + + p_uniforms.setValue( _gl, 'viewMatrices', [camera.matrixWorldInverse, newMat] ); + + } + + } else { + + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + + } } diff --git a/src/renderers/webgl/WebGLCapabilities.js b/src/renderers/webgl/WebGLCapabilities.js index 4eafd2132f..70c219dbe2 100644 --- a/src/renderers/webgl/WebGLCapabilities.js +++ b/src/renderers/webgl/WebGLCapabilities.js @@ -86,6 +86,13 @@ function WebGLCapabilities( gl, extensions, parameters ) { var maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; + var multiview = isWebGL2 && ( !! extensions.get( 'WEBGL_multiview' ) || !! extensions.get( 'OVR_multiview' ) || !! extensions.get( 'OVR_multiview2' ) ); +/* + var ovrMultiview2 = extensions.get( 'OVR_multiview2' ); + if (ovrMultiview2) { + var num = gl.getParameter(ovrMultiview2.MAX_VIEWS_OVR); + } +*/ return { isWebGL2: isWebGL2, @@ -110,7 +117,9 @@ function WebGLCapabilities( gl, extensions, parameters ) { floatFragmentTextures: floatFragmentTextures, floatVertexTextures: floatVertexTextures, - maxSamples: maxSamples + maxSamples: maxSamples, + + multiview: multiview }; diff --git a/src/renderers/webgl/WebGLMultiview.js b/src/renderers/webgl/WebGLMultiview.js new file mode 100644 index 0000000000..0b92eff518 --- /dev/null +++ b/src/renderers/webgl/WebGLMultiview.js @@ -0,0 +1,105 @@ + +function err() { + + console.error( bgl.getError() ); + +} + +var bgl; + +function WebGLMultiview( gl, canvas, extensions ) { + + var NUM_MULTIVIEW_VIEWS = 2; + bgl = gl; + var width = canvas.width; + var height = canvas.height; + var g_multiviewFb; // multiview framebuffer. + var g_multiviewViewFb; // single views inside the multiview framebuffer. + var g_multiviewColorTexture; // Color texture for multiview framebuffer. + var g_multiviewDepth; // Depth texture for multiview framebuffer. + var g_multiviewFbWidth = 0; + var g_multiviewFbHeight = 0; + + + var ext = extensions.get( 'OVR_multiview2' ); + if (!ext) { + return; + } + + var texture = { + color: null, + depthStencil: null + }; + + var framebuffer = gl.createFramebuffer(); + var multiviewViewFb = null; + + this.createMultiviewRenderTargetTexture = function () { + + var halfWidth = Math.floor( canvas.width * 0.5 ); + + framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); + + texture.color = gl.createTexture(); + gl.bindTexture( gl.TEXTURE_2D_ARRAY, texture.color ); + gl.texParameteri( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + gl.texParameteri( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texImage3D( gl.TEXTURE_2D_ARRAY, 0, gl.RGBA8, halfWidth, canvas.height, NUM_MULTIVIEW_VIEWS, 0, gl.RGBA, gl.UNSIGNED_BYTE, null ); + ext.framebufferTextureMultiviewOVR( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.color, 0, 0, NUM_MULTIVIEW_VIEWS ); + + texture.depthStencil = gl.createTexture(); + gl.bindTexture( gl.TEXTURE_2D_ARRAY, texture.depthStencil ); + gl.texParameteri( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + gl.texParameteri( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texImage3D( gl.TEXTURE_2D_ARRAY, 0, gl.DEPTH24_STENCIL8, halfWidth, canvas.height, NUM_MULTIVIEW_VIEWS, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null ); + ext.framebufferTextureMultiviewOVR( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, texture.depthStencil, 0, 0, NUM_MULTIVIEW_VIEWS ); + + multiviewViewFb = [ null, null ]; + for ( var viewIndex = 0; viewIndex < 2; ++ viewIndex ) { + + multiviewViewFb[ viewIndex ] = gl.createFramebuffer(); + gl.bindFramebuffer( gl.FRAMEBUFFER, multiviewViewFb[ viewIndex ] ); + gl.framebufferTextureLayer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.color, 0, viewIndex ); + + } + g_multiviewFbWidth = halfWidth; + g_multiviewFbHeight = canvas.height; +}; + + this.bindMultiviewFrameBuffer = function () { + + var halfWidth = Math.floor( canvas.width * 0.5 ); + if ( g_multiviewFbWidth < halfWidth || g_multiviewFbHeight < canvas.height ) { + console.log( 'Updating multiview FBO with dimensions: ', halfWidth, canvas.height ); + gl.bindTexture( gl.TEXTURE_2D_ARRAY, texture.color ); + gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.RGBA8, halfWidth, canvas.height, NUM_MULTIVIEW_VIEWS, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindTexture( gl.TEXTURE_2D_ARRAY, texture.depthStencil ); + gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.DEPTH24_STENCIL8, halfWidth, canvas.height, NUM_MULTIVIEW_VIEWS, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null); + g_multiviewFbWidth = halfWidth; + g_multiviewFbHeight = canvas.height; + + } + // gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, framebuffer ); + + }; + + this.unbindMultiviewFrameBuffer = function () { + + var halfWidth = Math.floor( canvas.width * 0.5 ); + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, multiviewViewFb[ 0 ] ); + gl.blitFramebuffer( 0, 0, halfWidth, canvas.height, 0, 0, halfWidth, canvas.height, gl.COLOR_BUFFER_BIT, gl.NEAREST ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, multiviewViewFb[ 1 ] ); + gl.blitFramebuffer( 0, 0, halfWidth, canvas.height, halfWidth, 0, canvas.width, canvas.height, gl.COLOR_BUFFER_BIT, gl.NEAREST ); + + }; + + this.createMultiviewRenderTargetTexture(); + +} + + + +export { WebGLMultiview }; diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index 81b47a2866..b660b98fff 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -427,14 +427,27 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters, parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', parameters.logarithmicDepthBuffer && ( capabilities.isWebGL2 || extensions.get( 'EXT_frag_depth' ) ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', 'uniform vec3 cameraPosition;', + renderer.vr.multiview ? [ + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrices[2];', + 'uniform mat3 normalMatrix;', + + 'uniform mat4 viewMatrices[2];', + '#define viewMatrix viewMatrices[VIEW_ID]', + '#define projectionMatrix projectionMatrices[VIEW_ID]' + + ].join( '\n' ) : [ + + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;', + + ].join( '\n' ), + 'attribute vec3 position;', 'attribute vec3 normal;', 'attribute vec2 uv;', @@ -551,9 +564,15 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters, ( ( material.extensions ? material.extensions.shaderTextureLOD : false ) || parameters.envMap ) && ( capabilities.isWebGL2 || extensions.get( 'EXT_shader_texture_lod' ) ) ? '#define TEXTURE_LOD_EXT' : '', - 'uniform mat4 viewMatrix;', 'uniform vec3 cameraPosition;', + renderer.vr.multiview ? [ + + 'uniform mat4 viewMatrices[2];', + '#define viewMatrix viewMatrices[VIEW_ID]' + + ].join( '\n' ) : 'uniform mat4 viewMatrix;', + ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', @@ -607,6 +626,15 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters, // GLSL 3.0 conversion prefixVertex = [ '#version 300 es\n', + + renderer.vr.multiview ? [ + + '#extension GL_OVR_multiview2 : require', + 'layout(num_views = 2) in;', + '#define VIEW_ID gl_ViewID_OVR' + + ].join( '\n' ) : '', + '#define attribute in', '#define varying out', '#define texture2D texture' @@ -614,6 +642,12 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters, prefixFragment = [ '#version 300 es\n', + renderer.vr.multiview ? [ + + '#extension GL_OVR_multiview2 : require', + '#define VIEW_ID gl_ViewID_OVR' + + ].join( '\n' ) : '', '#define varying in', isGLSL3ShaderMaterial ? '' : 'out highp vec4 pc_fragColor;', isGLSL3ShaderMaterial ? '' : '#define gl_FragColor pc_fragColor', @@ -634,11 +668,16 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters, var vertexGlsl = prefixVertex + vertexShader; var fragmentGlsl = prefixFragment + fragmentShader; +<<<<<<< HEAD // console.log( '*VERTEX*', vertexGlsl ); // console.log( '*FRAGMENT*', fragmentGlsl ); var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); +======= + var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl, renderer.debug.checkShaderErrors ); + var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl, renderer.debug.checkShaderErrors ); +>>>>>>> Initial working implementation gl.attachShader( program, glVertexShader ); gl.attachShader( program, glFragmentShader ); -- GitLab