Просмотр исходного кода

FrustumArray: Optimize frustum computations. (#33804)

Michael Herzog 3 недель назад
Родитель
Сommit
4d1d644254
3 измененных файлов с 134 добавлено и 148 удалено
  1. 103 133
      src/math/FrustumArray.js
  2. 19 11
      src/objects/BatchedMesh.js
  3. 12 4
      src/renderers/common/Renderer.js

+ 103 - 133
src/math/FrustumArray.js

@@ -3,7 +3,6 @@ import { Frustum } from './Frustum.js';
 import { Matrix4 } from './Matrix4.js';
 
 const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
-const _frustum = /*@__PURE__*/ new Frustum();
 
 /**
  * FrustumArray is used to determine if an object is visible in at least one camera
@@ -25,220 +24,191 @@ class FrustumArray {
 		 */
 		this.coordinateSystem = WebGLCoordinateSystem;
 
+		/**
+		 * A pool of frustum instances. It may hold more entries than are
+		 * currently in use; surplus instances are kept for reuse to avoid
+		 * reallocating when array cameras of different lengths are rendered.
+		 *
+		 * @private
+		 * @type {Array<Frustum>}
+		 */
+		this._frustums = [];
+
+		/**
+		 * The number of frustums in {@link FrustumArray#_frustums} that are currently
+		 * in use.
+		 *
+		 * @private
+		 * @type {number}
+		 * @default 0
+		 */
+		this._count = 0;
+
 	}
 
 	/**
-	 * Returns `true` if the 3D object's bounding sphere is intersecting any frustum
-	 * from the camera array.
+	 * Computes and caches a frustum for each camera of the given array camera.
 	 *
-	 * @param {Object3D} object - The 3D object to test.
-	 * @param {Object} cameraArray - An object with a cameras property containing an array of cameras.
-	 * @return {boolean} Whether the 3D object is visible in any camera.
+	 * @param {ArrayCamera} cameraArray - The array camera whose sub-cameras define the frustums.
+	 * @return {FrustumArray} A reference to this frustum array.
 	 */
-	intersectsObject( object, cameraArray ) {
-
-		if ( ! cameraArray.isArrayCamera || cameraArray.cameras.length === 0 ) {
-
-			return false;
-
-		}
+	setFromArrayCamera( cameraArray ) {
 
-		for ( let i = 0; i < cameraArray.cameras.length; i ++ ) {
+		const cameras = cameraArray.cameras;
+		const frustums = this._frustums;
 
-			const camera = cameraArray.cameras[ i ];
+		for ( let i = 0; i < cameras.length; i ++ ) {
 
-			_projScreenMatrix.multiplyMatrices(
-				camera.projectionMatrix,
-				camera.matrixWorldInverse
-			);
+			const camera = cameras[ i ];
 
-			_frustum.setFromProjectionMatrix(
-				_projScreenMatrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
+			_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
 
-			if ( _frustum.intersectsObject( object ) ) {
+			if ( frustums[ i ] === undefined ) frustums[ i ] = new Frustum();
 
-				return true; // Object is visible in at least one camera
-
-			}
+			frustums[ i ].setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth );
 
 		}
 
-		return false; // Not visible in any camera
+		this._count = cameras.length;
+
+		return this;
 
 	}
 
 	/**
-	 * Returns `true` if the given sprite is intersecting any frustum
-	 * from the camera array.
+	 * Returns `true` if the 3D object's bounding sphere is intersecting any cached frustum.
 	 *
-	 * @param {Sprite} sprite - The sprite to test.
-	 * @param {Object} cameraArray - An object with a cameras property containing an array of cameras.
-	 * @return {boolean} Whether the sprite is visible in any camera.
+	 * {@link FrustumArray#setFromArrayCamera} must be called once per render before this method.
+	 *
+	 * @param {Object3D} object - The 3D object to test.
+	 * @return {boolean} Whether the 3D object is visible in any camera.
 	 */
-	intersectsSprite( sprite, cameraArray ) {
+	intersectsObject( object ) {
 
-		if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) {
+		const frustums = this._frustums;
 
-			return false;
+		for ( let i = 0; i < this._count; i ++ ) {
 
-		}
+			if ( frustums[ i ].intersectsObject( object ) ) return true;
 
-		for ( let i = 0; i < cameraArray.cameras.length; i ++ ) {
+		}
 
-			const camera = cameraArray.cameras[ i ];
+		return false;
 
-			_projScreenMatrix.multiplyMatrices(
-				camera.projectionMatrix,
-				camera.matrixWorldInverse
-			);
+	}
 
-			_frustum.setFromProjectionMatrix(
-				_projScreenMatrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
+	/**
+	 * Returns `true` if the given sprite is intersecting any cached frustum.
+	 *
+	 * {@link FrustumArray#setFromArrayCamera} must be called once per render before this method.
+	 *
+	 * @param {Sprite} sprite - The sprite to test.
+	 * @return {boolean} Whether the sprite is visible in any camera.
+	 */
+	intersectsSprite( sprite ) {
 
-			if ( _frustum.intersectsSprite( sprite ) ) {
+		const frustums = this._frustums;
 
-				return true; // Sprite is visible in at least one camera
+		for ( let i = 0; i < this._count; i ++ ) {
 
-			}
+			if ( frustums[ i ].intersectsSprite( sprite ) ) return true;
 
 		}
 
-		return false; // Not visible in any camera
+		return false;
 
 	}
 
 	/**
-	 * Returns `true` if the given bounding sphere is intersecting any frustum
-	 * from the camera array.
+	 * Returns `true` if the given bounding sphere is intersecting any cached frustum.
+	 *
+	 * {@link FrustumArray#setFromArrayCamera} must be called once per render before this method.
 	 *
 	 * @param {Sphere} sphere - The bounding sphere to test.
-	 * @param {Object} cameraArray - An object with a cameras property containing an array of cameras.
 	 * @return {boolean} Whether the sphere is visible in any camera.
 	 */
-	intersectsSphere( sphere, cameraArray ) {
-
-		if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) {
-
-			return false;
-
-		}
+	intersectsSphere( sphere ) {
 
-		for ( let i = 0; i < cameraArray.cameras.length; i ++ ) {
+		const frustums = this._frustums;
 
-			const camera = cameraArray.cameras[ i ];
+		for ( let i = 0; i < this._count; i ++ ) {
 
-			_projScreenMatrix.multiplyMatrices(
-				camera.projectionMatrix,
-				camera.matrixWorldInverse
-			);
-
-			_frustum.setFromProjectionMatrix(
-				_projScreenMatrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
-
-			if ( _frustum.intersectsSphere( sphere ) ) {
-
-				return true; // Sphere is visible in at least one camera
-
-			}
+			if ( frustums[ i ].intersectsSphere( sphere ) ) return true;
 
 		}
 
-		return false; // Not visible in any camera
+		return false;
 
 	}
 
 	/**
-	 * Returns `true` if the given bounding box is intersecting any frustum
-	 * from the camera array.
+	 * Returns `true` if the given bounding box is intersecting any cached frustum.
+	 *
+	 * {@link FrustumArray#setFromArrayCamera} must be called once per render before this method.
 	 *
 	 * @param {Box3} box - The bounding box to test.
-	 * @param {Object} cameraArray - An object with a cameras property containing an array of cameras.
 	 * @return {boolean} Whether the box is visible in any camera.
 	 */
-	intersectsBox( box, cameraArray ) {
-
-		if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) {
-
-			return false;
+	intersectsBox( box ) {
 
-		}
-
-		for ( let i = 0; i < cameraArray.cameras.length; i ++ ) {
-
-			const camera = cameraArray.cameras[ i ];
-
-			_projScreenMatrix.multiplyMatrices(
-				camera.projectionMatrix,
-				camera.matrixWorldInverse
-			);
+		const frustums = this._frustums;
 
-			_frustum.setFromProjectionMatrix(
-				_projScreenMatrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
+		for ( let i = 0; i < this._count; i ++ ) {
 
-			if ( _frustum.intersectsBox( box ) ) {
-
-				return true; // Box is visible in at least one camera
-
-			}
+			if ( frustums[ i ].intersectsBox( box ) ) return true;
 
 		}
 
-		return false; // Not visible in any camera
+		return false;
 
 	}
 
 	/**
-	 * Returns `true` if the given point lies within any frustum
-	 * from the camera array.
+	 * Returns `true` if the given point lies within any cached frustum.
+	 *
+	 * {@link FrustumArray#setFromArrayCamera} must be called once per render before this method.
 	 *
 	 * @param {Vector3} point - The point to test.
-	 * @param {Object} cameraArray - An object with a cameras property containing an array of cameras.
 	 * @return {boolean} Whether the point is visible in any camera.
 	 */
-	containsPoint( point, cameraArray ) {
+	containsPoint( point ) {
 
-		if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) {
+		const frustums = this._frustums;
 
-			return false;
+		for ( let i = 0; i < this._count; i ++ ) {
+
+			if ( frustums[ i ].containsPoint( point ) ) return true;
 
 		}
 
-		for ( let i = 0; i < cameraArray.cameras.length; i ++ ) {
+		return false;
 
-			const camera = cameraArray.cameras[ i ];
+	}
 
-			_projScreenMatrix.multiplyMatrices(
-				camera.projectionMatrix,
-				camera.matrixWorldInverse
-			);
+	/**
+	 * Copies the values of the given frustum array to this instance.
+	 *
+	 * @param {FrustumArray} frustumArray - The frustum array to copy.
+	 * @return {FrustumArray} A reference to this frustum array.
+	 */
+	copy( source ) {
+
+		this.coordinateSystem = source.coordinateSystem;
 
-			_frustum.setFromProjectionMatrix(
-				_projScreenMatrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
+		const frustums = this._frustums;
+		const sourceFrustums = source._frustums;
 
-			if ( _frustum.containsPoint( point ) ) {
+		for ( let i = 0; i < source._count; i ++ ) {
 
-				return true; // Point is visible in at least one camera
+			if ( frustums[ i ] === undefined ) frustums[ i ] = new Frustum();
 
-			}
+			frustums[ i ].copy( sourceFrustums[ i ] );
 
 		}
 
-		return false; // Not visible in any camera
+		this._count = source._count;
+
+		return this;
 
 	}
 
@@ -249,7 +219,7 @@ class FrustumArray {
 	 */
 	clone() {
 
-		return new FrustumArray();
+		return new FrustumArray().copy( this );
 
 	}
 

+ 19 - 11
src/objects/BatchedMesh.js

@@ -1552,17 +1552,25 @@ class BatchedMesh extends Mesh {
 
 		const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
 		// prepare the frustum in the local frame
-		if ( perObjectFrustumCulled && ! camera.isArrayCamera ) {
+		if ( perObjectFrustumCulled ) {
 
-			_matrix
-				.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse )
-				.multiply( this.matrixWorld );
+			if ( camera.isArrayCamera ) {
 
-			_frustum.setFromProjectionMatrix(
-				_matrix,
-				camera.coordinateSystem,
-				camera.reversedDepth
-			);
+				frustum.setFromArrayCamera( camera );
+
+			} else {
+
+				_matrix
+					.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse )
+					.multiply( this.matrixWorld );
+
+				frustum.setFromProjectionMatrix(
+					_matrix,
+					camera.coordinateSystem,
+					camera.reversedDepth
+				);
+
+			}
 
 		}
 
@@ -1588,7 +1596,7 @@ class BatchedMesh extends Mesh {
 					let culled = false;
 					if ( perObjectFrustumCulled ) {
 
-						culled = ! frustum.intersectsSphere( _sphere, camera );
+						culled = ! frustum.intersectsSphere( _sphere );
 
 					}
 
@@ -1645,7 +1653,7 @@ class BatchedMesh extends Mesh {
 						// get the bounds in world space
 						this.getMatrixAt( i, _matrix );
 						this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix );
-						culled = ! frustum.intersectsSphere( _sphere, camera );
+						culled = ! frustum.intersectsSphere( _sphere );
 
 					}
 

+ 12 - 4
src/renderers/common/Renderer.js

@@ -948,7 +948,11 @@ class Renderer {
 
 		const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
 
-		if ( ! camera.isArrayCamera ) {
+		if ( camera.isArrayCamera ) {
+
+			frustum.setFromArrayCamera( camera );
+
+		} else {
 
 			_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
 			frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth );
@@ -1653,7 +1657,11 @@ class Renderer {
 
 		const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
 
-		if ( ! camera.isArrayCamera ) {
+		if ( camera.isArrayCamera ) {
+
+			frustum.setFromArrayCamera( camera );
+
+		} else {
 
 			_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
 			frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth );
@@ -3094,7 +3102,7 @@ class Renderer {
 
 				const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
 
-				if ( ! object.frustumCulled || frustum.intersectsSprite( object, camera ) ) {
+				if ( ! object.frustumCulled || frustum.intersectsSprite( object ) ) {
 
 					if ( this.sortObjects === true ) {
 
@@ -3120,7 +3128,7 @@ class Renderer {
 
 				const frustum = camera.isArrayCamera ? _frustumArray : _frustum;
 
-				if ( ! object.frustumCulled || frustum.intersectsObject( object, camera ) ) {
+				if ( ! object.frustumCulled || frustum.intersectsObject( object ) ) {
 
 					const { geometry, material } = object;
 

粤ICP备19079148号