diff --git a/examples/jsm/objects/BatchedMesh.js b/examples/jsm/objects/BatchedMesh.js index b9362b197e03e7..37778c87d6c07a 100644 --- a/examples/jsm/objects/BatchedMesh.js +++ b/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(); @@ -29,6 +86,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 = []; @@ -77,6 +135,7 @@ class BatchedMesh extends Mesh { this.isBatchedMesh = true; this.perObjectFrustumCulled = true; + this.sortObjects = true; this.boundingBox = null; this.boundingSphere = null; @@ -816,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; @@ -886,30 +946,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 ++; + } } 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 ); } 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;