From cac261c9191dc90b41bc3115049e939a2369eaf1 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 10 Nov 2023 18:58:30 +0900 Subject: [PATCH 1/5] Add support for sorting objects to BatchedMesh --- examples/jsm/objects/BatchedMesh.js | 145 +++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 15 deletions(-) diff --git a/examples/jsm/objects/BatchedMesh.js b/examples/jsm/objects/BatchedMesh.js index 4909d8350dc3fb..4b19ce806ead19 100644 --- a/examples/jsm/objects/BatchedMesh.js +++ b/examples/jsm/objects/BatchedMesh.js @@ -15,6 +15,64 @@ import { Vector3, } from 'three'; +function sortOpaque( a, b ) { + + return a.z - b.z; + +} + +function sortTransparent( a, b ) { + + return b.z - a.z; + +} + +class RenderInfoPool { + + constructor() { + + this.index = 0; + this.pool = []; + this.list = []; + + } + + getNextItem( 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; + return item; + + } + + reset() { + + this.list.length = 0; + this.index = 0; + + } + +} + const ID_ATTR_NAME = 'batchId'; const _matrix = new Matrix4(); const _identityMatrix = new Matrix4(); @@ -29,6 +87,7 @@ const _frustum = new Frustum(); const _box = new Box3(); const _sphere = new Sphere(); const _vector = new Vector3(); +const _renderItems = new RenderInfoPool(); // @TODO: SkinnedMesh support? // @TODO: Future work if needed. Move into the core. Can be optimized more with WEBGL_multi_draw. @@ -75,6 +134,7 @@ class BatchedMesh extends Mesh { this.isBatchedMesh = true; this.perObjectFrustumCulled = true; + this.sortObjects = true; this.boundingBox = null; this.boundingSphere = null; @@ -827,30 +887,85 @@ 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 ) { + _vector.setFromMatrixPosition( camera.matrixWorld ); - // get the bounds in camera space - this.getMatrixAt( i, _matrix ); + let added = 0; + 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 ); + const z = _vector.distanceToSquared( _sphere.center ); + + // 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 || true ) { + + _renderItems.getNextItem( drawRanges[ i ], z ); + + } } - if ( ! culled ) { + } + + const list = _renderItems.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 ++; + + } + + _renderItems.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 ++; + } } From db237663f90db1955f264aab44b7c2763d89906c Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 10 Nov 2023 19:00:32 +0900 Subject: [PATCH 2/5] Add transparency to the demo --- examples/webgl_mesh_batch.html | 41 +++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/examples/webgl_mesh_batch.html b/examples/webgl_mesh_batch.html index 390fec942a0625..e8a38b317d0cf8 100644 --- a/examples/webgl_mesh_batch.html +++ b/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 ); } From f184dbf524253cc0dc1bed55e2a1bd2a7e24393b Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 10 Nov 2023 19:06:53 +0900 Subject: [PATCH 3/5] Class rename --- examples/jsm/objects/BatchedMesh.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/jsm/objects/BatchedMesh.js b/examples/jsm/objects/BatchedMesh.js index 4b19ce806ead19..c642bdc27e724f 100644 --- a/examples/jsm/objects/BatchedMesh.js +++ b/examples/jsm/objects/BatchedMesh.js @@ -27,7 +27,7 @@ function sortTransparent( a, b ) { } -class RenderInfoPool { +class MultiDrawRenderList { constructor() { @@ -37,7 +37,7 @@ class RenderInfoPool { } - getNextItem( drawRange, z ) { + push( drawRange, z ) { const pool = this.pool; const list = this.list; @@ -60,7 +60,6 @@ class RenderInfoPool { item.start = drawRange.start; item.count = drawRange.count; item.z = z; - return item; } @@ -87,7 +86,7 @@ const _frustum = new Frustum(); const _box = new Box3(); const _sphere = new Sphere(); const _vector = new Vector3(); -const _renderItems = new RenderInfoPool(); +const _renderList = new MultiDrawRenderList(); // @TODO: SkinnedMesh support? // @TODO: Future work if needed. Move into the core. Can be optimized more with WEBGL_multi_draw. @@ -916,7 +915,7 @@ class BatchedMesh extends Mesh { if ( ! culled || true ) { - _renderItems.getNextItem( drawRanges[ i ], z ); + _renderList.push( drawRanges[ i ], z ); } @@ -924,7 +923,7 @@ class BatchedMesh extends Mesh { } - const list = _renderItems.list; + const list = _renderList.list; list.sort( material.transparent ? sortTransparent : sortOpaque ); for ( let i = 0, l = list.length; i < l; i ++ ) { @@ -936,7 +935,7 @@ class BatchedMesh extends Mesh { } - _renderItems.reset(); + _renderList.reset(); } else { From 9d767faa52fff5b805271a5726b59dbf06e6fa9b Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 10 Nov 2023 19:15:49 +0900 Subject: [PATCH 4/5] Comments, removed variables, fixed condition --- examples/jsm/objects/BatchedMesh.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/jsm/objects/BatchedMesh.js b/examples/jsm/objects/BatchedMesh.js index 51d0a316f6839d..0a05888a5f9c86 100644 --- a/examples/jsm/objects/BatchedMesh.js +++ b/examples/jsm/objects/BatchedMesh.js @@ -948,16 +948,15 @@ class BatchedMesh extends Mesh { if ( this.sortObjects ) { + // get the camera position _vector.setFromMatrixPosition( camera.matrixWorld ); - let added = 0; for ( let i = 0, l = visible.length; i < l; i ++ ) { if ( visible[ i ] ) { this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix ); - const z = _vector.distanceToSquared( _sphere.center ); // determine whether the batched geometry is within the frustum let culled = false; @@ -972,8 +971,10 @@ class BatchedMesh extends Mesh { } - if ( ! culled || true ) { + if ( ! culled ) { + // get the distance from camera used for sorting + const z = _vector.distanceToSquared( _sphere.center ); _renderList.push( drawRanges[ i ], z ); } @@ -982,6 +983,7 @@ class BatchedMesh extends Mesh { } + // Sort the draw ranges and prep for rendering const list = _renderList.list; list.sort( material.transparent ? sortTransparent : sortOpaque ); From d1b68f83128e7be65096e191618c49519e8e99ed Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Mon, 13 Nov 2023 00:30:23 +0900 Subject: [PATCH 5/5] Add copy and toJSON support for object sorting --- examples/jsm/objects/BatchedMesh.js | 1 + src/core/Object3D.js | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/jsm/objects/BatchedMesh.js b/examples/jsm/objects/BatchedMesh.js index 0a05888a5f9c86..37778c87d6c07a 100644 --- a/examples/jsm/objects/BatchedMesh.js +++ b/examples/jsm/objects/BatchedMesh.js @@ -875,6 +875,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; diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 65d327bf4d5f01..4d0656c85f8ecd 100644 --- a/src/core/Object3D.js +++ b/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;