Browse Source

Implement setProjectionFromUnion on ArrayCamera so that VR can use a combined frustum for culling. Fixes #13236

Jordan Santell 6 years ago
parent
commit
fd5077bd21

+ 1 - 0
examples/files.js

@@ -325,6 +325,7 @@ var files = {
 		"webvr_ballshooter",
 		"webvr_cubes",
 		"webvr_dragging",
+		"webvr_frustum",
 		"webvr_lorenzattractor",
 		"webvr_panorama",
 		"webvr_paint",

+ 129 - 0
examples/webvr_frustum.html

@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webvr - frustum</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<!-- Origin Trial Token, feature = WebVR (For Chrome M62+), origin = https://threejs.org, expires = 2018-09-11 -->
+		<meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M62+)" data-expires="2018-09-11" content="AqhFUYKxq/d+E8CDT0fuYRCg8TvlTP52x0Jv7I9t27sLhR30LmcahBRfSwzP89ukjs2+ia99VrrLoRyaFAwJVA0AAABQeyJvcmlnaW4iOiJodHRwczovL3RocmVlanMub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJWUjEuMU02MiIsImV4cGlyeSI6MTUzNjYyNDAwMH0=">
+		<!-- Origin Trial Token, feature = WebXR Device API (For Chrome M69+), origin = https://threejs.org, expires = 2018-10-27 -->
+		<meta http-equiv="origin-trial" data-feature="WebXR Device API (For Chrome M69+)" data-expires="2018-10-27" content="An4ZYOGvf6kVHNxqZxS02TPAvpZESkmBhcVCM/byViDDuEB2XKvCF43aCJjrAU/R8H3WDlv+1bDGTL/XxstHGgoAAABTeyJvcmlnaW4iOiJodHRwczovL3RocmVlanMub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJYUkRldmljZU02OSIsImV4cGlyeSI6MTU0MDY1NTAyMn0=">
+		<!-- Origin Trial Token, feature = WebXR Gamepad Support, origin = https://threejs.org, expires = 2018-10-24 -->
+		<meta http-equiv="origin-trial" data-feature="WebXR Gamepad Support" data-expires="2018-10-24" content="Agrr6lZhlwzv5jmv/mpLZA37DIiVcg3HvX8bH8EWB+OBruV3sUJuzDfYz6qs/je+LcH41DkrmPn4k9RaUaqpQAAAAABYeyJvcmlnaW4iOiJodHRwczovL3RocmVlanMub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJYUkdhbWVwYWRTdXBwb3J0IiwiZXhwaXJ5IjoxNTQwMzg4NjI0fQ==">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #101010;
+				color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+			a {
+				color: #f00;
+			}
+		</style>
+	</head>
+	<body>
+
+		<script src="../build/three.js"></script>
+		<script src="js/vr/WebVR.js"></script>
+		<script src="js/geometries/BoxLineGeometry.js"></script>
+
+		<script>
+
+			var size = 10;
+
+			var container;
+			var camera, scene, raycaster, renderer;
+
+			var room;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				var info = document.createElement( 'div' );
+				info.style.position = 'absolute';
+				info.style.top = '10px';
+				info.style.width = '100%';
+				info.style.textAlign = 'center';
+				info.innerHTML = '<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - interactive cubes';
+				container.appendChild( info );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x505050 );
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 10 );
+				scene.add( camera );
+
+				room = new THREE.LineSegments(
+					new THREE.BoxLineGeometry( 6, 6, 6, 10, 10, 10 ),
+					new THREE.LineBasicMaterial( { color: 0x808080 } )
+				);
+				room.position.y = 3;
+				scene.add( room );
+
+				scene.add( new THREE.HemisphereLight( 0x606060, 0x404040 ) );
+
+				var geometry = new THREE.BoxBufferGeometry( 0.15, 0.15, 0.15 );
+
+				for ( var x = 0; x < size; x ++ ) {
+					for ( var y = 0; y < size; y ++ ) {
+
+						var xVal = x / size;
+						var yVal = y / size;
+						var color = new THREE.Color( 1, 1, 1 );
+						color.r = x % 2;
+						color.g = y % 2;
+						color.b = 1;
+						var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: color } ));
+
+						object.position.x = xVal * 8 - 4;
+						object.position.y = yVal * 8 - 4;
+						object.position.z = -4;
+
+						room.add( object );
+
+					}
+				}
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.vr.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				document.body.appendChild( WEBVR.createButton( renderer ) );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.setAnimationLoop( render );
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 61 - 1
src/cameras/ArrayCamera.js

@@ -3,6 +3,7 @@
  */
 
 import { PerspectiveCamera } from './PerspectiveCamera.js';
+import { Vector3 } from '../math/Vector3.js';
 
 function ArrayCamera( array ) {
 
@@ -16,7 +17,66 @@ ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototyp
 
 	constructor: ArrayCamera,
 
-	isArrayCamera: true
+	isArrayCamera: true,
+
+	/**
+	 * Assumes 2 cameras that are perpendicular 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.
+	 */
+	setProjectionFromUnion: function () {
+		var cameraLPos = new Vector3();
+		var cameraRPos = new Vector3();
+
+		return function () {
+			cameraLPos.setFromMatrixPosition( this.cameras[ 0 ].matrixWorld );
+			cameraRPos.setFromMatrixPosition( this.cameras[ 1 ].matrixWorld );
+
+			var ipd = cameraLPos.distanceTo( cameraRPos );
+
+			var projL = this.cameras[ 0 ].projectionMatrix;
+			var projR = this.cameras[ 1 ].projectionMatrix;
+
+			// VR systems will have identical far and near planes, and
+			// most likely identical top and bottom frustum extents.
+			// via: https://computergraphics.stackexchange.com/a/4765
+			var near = projL[ 14 ] / ( projL[ 10 ] - 1 );
+			var far = projL[ 14 ] / ( projL[ 10 ] + 1 );
+
+			var leftFovL = ( projL[ 8 ] - 1 ) / projL[ 0 ];
+			var rightFovR = ( projR[ 8 ] + 1 ) / projR[ 0 ];
+			var leftL = leftFovL * near;
+			var rightR = rightFovR * near;
+			var topL = near * ( projL[ 9 ] + 1 ) / projL[ 5 ];
+			var topR = near * ( projR[ 9 ] + 1 ) / projR[ 5 ];
+			var bottomL = near * ( projL[ 9 ] - 1 ) / projL[ 5 ];
+			var bottomR = near * ( projR[ 9 ] - 1 ) / projR[ 5 ];
+
+			// Calculate the new camera's position offset from the
+			// left camera.
+			var zOffset = ipd / (leftFovL + rightFovR);
+			var xOffset = zOffset * leftFovL;
+
+			// TODO: Better way to apply this offset?
+			this.cameras[ 0 ].matrixWorld.decompose( this.position, this.quaternion, this.scale );
+			this.translateX(xOffset);
+			this.translateZ(-zOffset);
+			this.matrixWorld.compose( this.position, this.quaternion, this.scale );
+			this.matrixWorldInverse.getInverse(this.matrixWorld);
+
+			// 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 left = leftL - xOffset;
+			var right = rightR + (ipd - xOffset)
+			var top = Math.max( topL, topR );
+			var bottom = Math.min( bottomL, bottomR );
+
+			this.projectionMatrix.makePerspective( left, right, top, bottom, near2, far2 );
+		}
+	}(),
 
 } );
 

+ 1 - 7
src/renderers/webvr/WebVRManager.js

@@ -290,9 +290,6 @@ function WebVRManager( renderer ) {
 		cameraL.far = camera.far;
 		cameraR.far = camera.far;
 
-		cameraVR.matrixWorld.copy( camera.matrixWorld );
-		cameraVR.matrixWorldInverse.copy( camera.matrixWorldInverse );
-
 		cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );
 		cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );
 
@@ -326,10 +323,7 @@ function WebVRManager( renderer ) {
 		cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix );
 		cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix );
 
-		// HACK (mrdoob)
-		// https://github.com/w3c/webvr/issues/203
-
-		cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
+		cameraVR.setProjectionFromUnion();
 
 		//
 

+ 2 - 7
src/renderers/webvr/WebXRManager.js

@@ -161,8 +161,6 @@ function WebXRManager( renderer ) {
 			var parent = camera.parent;
 			var cameras = cameraVR.cameras;
 
-			// apply camera.parent to cameraVR
-
 			updateCamera( cameraVR, parent );
 
 			for ( var i = 0; i < cameras.length; i ++ ) {
@@ -183,6 +181,8 @@ function WebXRManager( renderer ) {
 
 			}
 
+			cameraVR.setProjectionFromUnion();
+
 			return cameraVR;
 
 		}
@@ -221,11 +221,6 @@ function WebXRManager( renderer ) {
 
 					cameraVR.matrix.copy( camera.matrix );
 
-					// HACK (mrdoob)
-					// https://github.com/w3c/webvr/issues/203
-
-					cameraVR.projectionMatrix.copy( camera.projectionMatrix );
-
 				}
 
 			}