浏览代码

BatchedMesh: Add support for per-object opaque and transparent sorting (#27168)

* Add support for sorting objects to BatchedMesh

* Add transparency to the demo

* Class rename

* Comments, removed variables, fixed condition

* Add copy and toJSON support for object sorting
Garrett Johnson 1 年之前
父节点
当前提交
fec1161ac8
共有 3 个文件被更改,包括 171 次插入18 次删除
  1. 132 15
      examples/jsm/objects/BatchedMesh.js
  2. 38 3
      examples/webgl_mesh_batch.html
  3. 1 0
      src/core/Object3D.js

+ 132 - 15
examples/jsm/objects/BatchedMesh.js

@@ -15,6 +15,63 @@ import {
 	Vector3,
 } from 'three';
 
+function sortOpaque( a, b ) {
+
+	return a.z - b.z;
+
+}
+
+function sortTransparent( a, b ) {
+
+	return b.z - a.z;
+
+}
+
+class MultiDrawRenderList {
+
+	constructor() {
+
+		this.index = 0;
+		this.pool = [];
+		this.list = [];
+
+	}
+
+	push( drawRange, z ) {
+
+		const pool = this.pool;
+		const list = this.list;
+		if ( this.index >= pool.length ) {
+
+			pool.push( {
+
+				start: - 1,
+				count: - 1,
+				z: - 1,
+
+			} );
+
+		}
+
+		const item = pool[ this.index ];
+		list.push( item );
+		this.index ++;
+
+		item.start = drawRange.start;
+		item.count = drawRange.count;
+		item.z = z;
+
+	}
+
+	reset() {
+
+		this.list.length = 0;
+		this.index = 0;
+
+	}
+
+}
+
 const ID_ATTR_NAME = 'batchId';
 const _matrix = new Matrix4();
 const _identityMatrix = new Matrix4();
@@ -23,6 +80,7 @@ const _frustum = new Frustum();
 const _box = new Box3();
 const _sphere = new Sphere();
 const _vector = new Vector3();
+const _renderList = new MultiDrawRenderList();
 const _mesh = new Mesh();
 const _batchIntersects = [];
 
@@ -72,6 +130,7 @@ class BatchedMesh extends Mesh {
 
 		this.isBatchedMesh = true;
 		this.perObjectFrustumCulled = true;
+		this.sortObjects = true;
 		this.boundingBox = null;
 		this.boundingSphere = null;
 
@@ -799,6 +858,7 @@ class BatchedMesh extends Mesh {
 
 		this.geometry = source.geometry.clone();
 		this.perObjectFrustumCulled = source.perObjectFrustumCulled;
+		this.sortObjects = source.sortObjects;
 		this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null;
 		this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null;
 
@@ -869,30 +929,87 @@ class BatchedMesh extends Mesh {
 		}
 
 		let count = 0;
-		for ( let i = 0, l = visible.length; i < l; i ++ ) {
 
-			if ( visible[ i ] ) {
+		if ( this.sortObjects ) {
 
-				// determine whether the batched geometry is within the frustum
-				let culled = false;
-				if ( perObjectFrustumCulled ) {
+			// get the camera position
+			_vector.setFromMatrixPosition( camera.matrixWorld );
 
-					// get the bounds in camera space
-					this.getMatrixAt( i, _matrix );
+			for ( let i = 0, l = visible.length; i < l; i ++ ) {
 
-					// get the bounds
-					this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix );
+				if ( visible[ i ] ) {
+
+					this.getMatrixAt( i, _matrix );
 					this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix );
-					culled = ! _frustum.intersectsBox( _box ) || ! _frustum.intersectsSphere( _sphere );
+
+					// determine whether the batched geometry is within the frustum
+					let culled = false;
+					if ( perObjectFrustumCulled ) {
+
+						// get the bounds in camera space
+						this.getMatrixAt( i, _matrix );
+
+						// get the bounds
+						this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix );
+						culled = ! _frustum.intersectsBox( _box ) || ! _frustum.intersectsSphere( _sphere );
+
+					}
+
+					if ( ! culled ) {
+
+						// get the distance from camera used for sorting
+						const z = _vector.distanceToSquared( _sphere.center );
+						_renderList.push( drawRanges[ i ], z );
+
+					}
 
 				}
 
-				if ( ! culled ) {
+			}
+
+			// Sort the draw ranges and prep for rendering
+			const list = _renderList.list;
+			list.sort( material.transparent ? sortTransparent : sortOpaque );
+
+			for ( let i = 0, l = list.length; i < l; i ++ ) {
+
+				const item = list[ i ];
+				multiDrawStarts[ count ] = item.start * bytesPerElement;
+				multiDrawCounts[ count ] = item.count;
+				count ++;
+
+			}
+
+			_renderList.reset();
+
+		} else {
+
+			for ( let i = 0, l = visible.length; i < l; i ++ ) {
+
+				if ( visible[ i ] ) {
+
+					// determine whether the batched geometry is within the frustum
+					let culled = false;
+					if ( perObjectFrustumCulled ) {
+
+						// get the bounds in camera space
+						this.getMatrixAt( i, _matrix );
+
+						// get the bounds
+						this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix );
+						this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix );
+						culled = ! _frustum.intersectsBox( _box ) || ! _frustum.intersectsSphere( _sphere );
+
+					}
+
+					if ( ! culled ) {
+
+						const range = drawRanges[ i ];
+						multiDrawStarts[ count ] = range.start * bytesPerElement;
+						multiDrawCounts[ count ] = range.count;
+						count ++;
 
-					const range = drawRanges[ i ];
-					multiDrawStarts[ count ] = range.start * bytesPerElement;
-					multiDrawCounts[ count ] = range.count;
-					count ++;
+					}
 
 				}
 

+ 38 - 3
examples/webgl_mesh_batch.html

@@ -39,7 +39,7 @@
 
 		let stats, gui, guiStatsEl;
 		let camera, controls, scene, renderer;
-		let geometries, mesh;
+		let geometries, mesh, material;
 		const ids = [];
 		const matrix = new THREE.Matrix4();
 
@@ -62,7 +62,10 @@
 		const api = {
 			method: Method.BATCHED,
 			count: 256,
-			dynamic: 16
+			dynamic: 16,
+
+			sortObjects: true,
+			opacity: 1,
 		};
 
 		init();
@@ -111,7 +114,14 @@
 
 		function createMaterial() {
 
-			return new THREE.MeshNormalMaterial();
+			if ( ! material ) {
+
+				material = new THREE.MeshNormalMaterial();
+				material.opacity = 0.1;
+
+			}
+
+			return material;
 
 		}
 
@@ -238,6 +248,25 @@
 			gui.add( api, 'count', 1, MAX_GEOMETRY_COUNT ).step( 1 ).onChange( initMesh );
 			gui.add( api, 'dynamic', 0, MAX_GEOMETRY_COUNT ).step( 1 );
 			gui.add( api, 'method', Method ).onChange( initMesh );
+			gui.add( api, 'opacity', 0, 1 ).onChange( v => {
+
+				if ( v < 1 ) {
+
+					material.transparent = true;
+					material.depthWrite = false;
+
+				} else {
+
+					material.transparent = false;
+					material.depthWrite = true;
+
+				}
+
+				material.opacity = v;
+				material.needsUpdate = true;
+
+			} );
+			gui.add( api, 'sortObjects' );
 
 			guiStatsEl = document.createElement( 'li' );
 			guiStatsEl.classList.add( 'gui-stats' );
@@ -313,6 +342,12 @@
 
 		function render() {
 
+			if ( mesh.isBatchedMesh ) {
+
+				mesh.sortObjects = api.sortObjects;
+
+			}
+
 			renderer.render( scene, camera );
 
 		}

+ 1 - 0
src/core/Object3D.js

@@ -723,6 +723,7 @@ class Object3D extends EventDispatcher {
 
 			object.type = 'BatchedMesh';
 			object.perObjectFrustumCulled = this.perObjectFrustumCulled;
+			object.sortObjects = this.sortObjects;
 
 			object.drawRanges = this._drawRanges;
 			object.reservedRanges = this._reservedRanges;