diff --git a/examples/webgl_mesh_batch.html b/examples/webgl_mesh_batch.html index 0bf42b24eba25b..326679dd6457ac 100644 --- a/examples/webgl_mesh_batch.html +++ b/examples/webgl_mesh_batch.html @@ -179,8 +179,8 @@ function initBatchedMesh() { const geometryCount = api.count; - const vertexCount = api.count * 512; - const indexCount = api.count * 1024; + const vertexCount = geometries.length * 512; + const indexCount = geometries.length * 1024; const euler = new THREE.Euler(); const matrix = new THREE.Matrix4(); @@ -192,9 +192,15 @@ ids.length = 0; + const geometryIds = [ + mesh.addGeometry( geometries[ 0 ] ), + mesh.addGeometry( geometries[ 1 ] ), + mesh.addGeometry( geometries[ 2 ] ), + ]; + for ( let i = 0; i < api.count; i ++ ) { - const id = mesh.addGeometry( geometries[ i % geometries.length ] ); + const id = mesh.addInstance( geometryIds[ i % geometryIds.length ] ); mesh.setMatrixAt( id, randomizeMatrix( matrix ) ); const rotationMatrix = new THREE.Matrix4(); diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js index 19cba48eef205b..3daafb219e5586 100644 --- a/src/objects/BatchedMesh.js +++ b/src/objects/BatchedMesh.js @@ -1,7 +1,7 @@ import { BufferAttribute } from '../core/BufferAttribute.js'; import { BufferGeometry } from '../core/BufferGeometry.js'; import { DataTexture } from '../textures/DataTexture.js'; -import { FloatType } from '../constants.js'; +import { FloatType, RedIntegerFormat, UnsignedIntType } from '../constants.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Mesh } from './Mesh.js'; import { RGBAFormat } from '../constants.js'; @@ -34,7 +34,7 @@ class MultiDrawRenderList { } - push( drawRange, z ) { + push( drawRange, z, index ) { const pool = this.pool; const list = this.list; @@ -45,6 +45,7 @@ class MultiDrawRenderList { start: - 1, count: - 1, z: - 1, + index: - 1, } ); @@ -57,6 +58,7 @@ class MultiDrawRenderList { item.start = drawRange.start; item.count = drawRange.count; item.z = z; + item.index = index; } @@ -69,7 +71,6 @@ class MultiDrawRenderList { } -const ID_ATTR_NAME = 'batchId'; const _matrix = /*@__PURE__*/ new Matrix4(); const _invMatrixWorld = /*@__PURE__*/ new Matrix4(); const _identityMatrix = /*@__PURE__*/ new Matrix4(); @@ -124,13 +125,13 @@ function copyAttributeData( src, target, targetOffset = 0 ) { class BatchedMesh extends Mesh { - get maxGeometryCount() { + get maxInstanceCount() { - return this._maxGeometryCount; + return this._maxInstanceCount; } - constructor( maxGeometryCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { + constructor( maxInstanceCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { super( new BufferGeometry(), material ); @@ -141,32 +142,33 @@ class BatchedMesh extends Mesh { this.boundingSphere = null; this.customSort = null; + // stores visible, active, and geometry id per object + this._drawInfo = []; + + // geometry information this._drawRanges = []; this._reservedRanges = []; - - this._visibility = []; - this._active = []; this._bounds = []; - this._maxGeometryCount = maxGeometryCount; + this._maxInstanceCount = maxInstanceCount; this._maxVertexCount = maxVertexCount; this._maxIndexCount = maxIndexCount; this._geometryInitialized = false; this._geometryCount = 0; - this._multiDrawCounts = new Int32Array( maxGeometryCount ); - this._multiDrawStarts = new Int32Array( maxGeometryCount ); + this._multiDrawCounts = new Int32Array( maxInstanceCount ); + this._multiDrawStarts = new Int32Array( maxInstanceCount ); this._multiDrawCount = 0; this._multiDrawInstances = null; this._visibilityChanged = true; // Local matrix per geometry by using data texture this._matricesTexture = null; + this._indirectTexture = null; + this._colorsTexture = null; this._initMatricesTexture(); - - // Local color per geometry by using data texture - this._colorsTexture = null; + this._initIndirectTexture(); } @@ -179,7 +181,7 @@ class BatchedMesh extends Mesh { // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) - let size = Math.sqrt( this._maxGeometryCount * 4 ); // 4 pixels needed for 1 matrix + let size = Math.sqrt( this._maxInstanceCount * 4 ); // 4 pixels needed for 1 matrix size = Math.ceil( size / 4 ) * 4; size = Math.max( size, 4 ); @@ -190,9 +192,21 @@ class BatchedMesh extends Mesh { } + _initIndirectTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + const indirectArray = new Uint32Array( size * size ); + const indirectTexture = new DataTexture( indirectArray, size, size, RedIntegerFormat, UnsignedIntType ); + + this._indirectTexture = indirectTexture; + + } + _initColorsTexture() { - let size = Math.sqrt( this._maxGeometryCount ); + let size = Math.sqrt( this._maxIndexCount ); size = Math.ceil( size ); // 4 floats per RGBA pixel initialized to white @@ -208,7 +222,6 @@ class BatchedMesh extends Mesh { const geometry = this.geometry; const maxVertexCount = this._maxVertexCount; - const maxGeometryCount = this._maxGeometryCount; const maxIndexCount = this._maxIndexCount; if ( this._geometryInitialized === false ) { @@ -234,11 +247,6 @@ class BatchedMesh extends Mesh { } - const idArray = maxGeometryCount > 65536 - ? new Uint32Array( maxVertexCount ) - : new Uint16Array( maxVertexCount ); - geometry.setAttribute( ID_ATTR_NAME, new BufferAttribute( idArray, 1 ) ); - this._geometryInitialized = true; } @@ -248,13 +256,6 @@ class BatchedMesh extends Mesh { // Make sure the geometry is compatible with the existing combined geometry attributes _validateGeometry( geometry ) { - // check that the geometry doesn't have a version of our reserved id attribute - if ( geometry.getAttribute( ID_ATTR_NAME ) ) { - - throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` ); - - } - // check to ensure the geometries are using consistent attributes and indices const batchGeometry = this.geometry; if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) { @@ -265,12 +266,6 @@ class BatchedMesh extends Mesh { for ( const attributeName in batchGeometry.attributes ) { - if ( attributeName === ID_ATTR_NAME ) { - - continue; - - } - if ( ! geometry.hasAttribute( attributeName ) ) { throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); @@ -306,15 +301,16 @@ class BatchedMesh extends Mesh { const geometryCount = this._geometryCount; const boundingBox = this.boundingBox; - const active = this._active; + const drawInfo = this._drawInfo; boundingBox.makeEmpty(); for ( let i = 0; i < geometryCount; i ++ ) { - if ( active[ i ] === false ) continue; + if ( drawInfo[ i ].active === false ) continue; + const geometryId = drawInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix ); - this.getBoundingBoxAt( i, _box ).applyMatrix4( _matrix ); + this.getBoundingBoxAt( geometryId, _box ).applyMatrix4( _matrix ); boundingBox.union( _box ); } @@ -329,23 +325,59 @@ class BatchedMesh extends Mesh { } - const geometryCount = this._geometryCount; const boundingSphere = this.boundingSphere; - const active = this._active; + const drawInfo = this._drawInfo; boundingSphere.makeEmpty(); - for ( let i = 0; i < geometryCount; i ++ ) { + for ( let i = 0, l = drawInfo.length; i < l; i ++ ) { - if ( active[ i ] === false ) continue; + if ( drawInfo[ i ].active === false ) continue; + const geometryId = drawInfo[ i ].geometryIndex; this.getMatrixAt( i, _matrix ); - this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix ); + this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); boundingSphere.union( _sphere ); } } + addInstance( geometryId ) { + + // ensure we're not over geometry + if ( this._drawInfo.length >= this._maxInstanceCount ) { + + throw new Error( 'BatchedMesh: Maximum item count reached.' ); + + } + + this._drawInfo.push( { + + visible: true, + active: true, + geometryIndex: geometryId, + + } ); + + // initialize the matrix + const drawId = this._drawInfo.length - 1; + const matricesTexture = this._matricesTexture; + const matricesArray = matricesTexture.image.data; + _identityMatrix.toArray( matricesArray, drawId * 16 ); + matricesTexture.needsUpdate = true; + + const colorsTexture = this._colorsTexture; + if ( colorsTexture ) { + + _whiteColor.toArray( colorsTexture.image.data, drawId * 4 ); + colorsTexture.needsUpdate = true; + + } + + return drawId; + + } + addGeometry( geometry, vertexCount = - 1, indexCount = - 1 ) { this._initializeGeometry( geometry ); @@ -353,9 +385,9 @@ class BatchedMesh extends Mesh { this._validateGeometry( geometry ); // ensure we're not over geometry - if ( this._geometryCount >= this._maxGeometryCount ) { + if ( this._drawInfo.length >= this._maxInstanceCount ) { - throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); + throw new Error( 'BatchedMesh: Maximum item count reached.' ); } @@ -433,32 +465,10 @@ class BatchedMesh extends Mesh { } - const visibility = this._visibility; - const active = this._active; - const matricesTexture = this._matricesTexture; - const matricesArray = this._matricesTexture.image.data; - const colorsTexture = this._colorsTexture; - - // push new visibility states - visibility.push( true ); - active.push( true ); - // update id const geometryId = this._geometryCount; this._geometryCount ++; - // initialize matrix information - _identityMatrix.toArray( matricesArray, geometryId * 16 ); - matricesTexture.needsUpdate = true; - - // initialize the color to white - if ( colorsTexture !== null ) { - - _whiteColor.toArray( colorsTexture.image.data, geometryId * 4 ); - colorsTexture.needsUpdate = true; - - } - // add the reserved range and draw range objects reservedRanges.push( reservedRange ); drawRanges.push( { @@ -473,16 +483,6 @@ class BatchedMesh extends Mesh { sphere: new Sphere() } ); - // set the id for the geometry - const idAttribute = this.geometry.getAttribute( ID_ATTR_NAME ); - for ( let i = 0; i < reservedRange.vertexCount; i ++ ) { - - idAttribute.setX( reservedRange.vertexStart + i, geometryId ); - - } - - idAttribute.needsUpdate = true; - // update the geometry this.setGeometryAt( geometryId, geometry ); @@ -490,9 +490,9 @@ class BatchedMesh extends Mesh { } - setGeometryAt( id, geometry ) { + setGeometryAt( geometryId, geometry ) { - if ( id >= this._geometryCount ) { + if ( geometryId >= this._geometryCount ) { throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); @@ -504,7 +504,7 @@ class BatchedMesh extends Mesh { const hasIndex = batchGeometry.getIndex() !== null; const dstIndex = batchGeometry.getIndex(); const srcIndex = geometry.getIndex(); - const reservedRange = this._reservedRanges[ id ]; + const reservedRange = this._reservedRanges[ geometryId ]; if ( hasIndex && srcIndex.count > reservedRange.indexCount || @@ -520,12 +520,6 @@ class BatchedMesh extends Mesh { const vertexCount = reservedRange.vertexCount; for ( const attributeName in batchGeometry.attributes ) { - if ( attributeName === ID_ATTR_NAME ) { - - continue; - - } - // copy attribute data const srcAttribute = geometry.getAttribute( attributeName ); const dstAttribute = batchGeometry.getAttribute( attributeName ); @@ -574,7 +568,7 @@ class BatchedMesh extends Mesh { } // store the bounding boxes - const bound = this._bounds[ id ]; + const bound = this._bounds[ geometryId ]; if ( geometry.boundingBox !== null ) { bound.box.copy( geometry.boundingBox ); @@ -598,67 +592,54 @@ class BatchedMesh extends Mesh { } // set drawRange count - const drawRange = this._drawRanges[ id ]; + const drawRange = this._drawRanges[ geometryId ]; const posAttr = geometry.getAttribute( 'position' ); drawRange.count = hasIndex ? srcIndex.count : posAttr.count; this._visibilityChanged = true; - return id; + return geometryId; } + /* deleteGeometry( geometryId ) { - // Note: User needs to call optimize() afterward to pack the data. - - const active = this._active; - if ( geometryId >= active.length || active[ geometryId ] === false ) { - - return this; - - } - - active[ geometryId ] = false; - this._visibilityChanged = true; - - return this; + // TODO: delete geometry and associated instances } + */ - getInstanceCountAt( id ) { - - if ( this._multiDrawInstances === null ) return null; - - return this._multiDrawInstances[ id ]; + /* + deleteInstance( instanceId ) { - } - - setInstanceCountAt( id, instanceCount ) { + // Note: User needs to call optimize() afterward to pack the data. - if ( this._multiDrawInstances === null ) { + const drawInfo = this._drawInfo; + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { - this._multiDrawInstances = new Int32Array( this._maxGeometryCount ).fill( 1 ); + return this; } - this._multiDrawInstances[ id ] = instanceCount; + drawInfo[ instanceId ].active = false; + this._visibilityChanged = true; - return id; + return this; } + */ // get bounding box and compute it if it doesn't exist - getBoundingBoxAt( id, target ) { + getBoundingBoxAt( geometryId, target ) { - const active = this._active; - if ( active[ id ] === false ) { + if ( geometryId >= this._geometryCount ) { return null; } // compute bounding box - const bound = this._bounds[ id ]; + const bound = this._bounds[ geometryId ]; const box = bound.box; const geometry = this.geometry; if ( bound.boxInitialized === false ) { @@ -667,7 +648,7 @@ class BatchedMesh extends Mesh { const index = geometry.index; const position = geometry.attributes.position; - const drawRange = this._drawRanges[ id ]; + const drawRange = this._drawRanges[ geometryId ]; for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { let iv = i; @@ -691,29 +672,28 @@ class BatchedMesh extends Mesh { } // get bounding sphere and compute it if it doesn't exist - getBoundingSphereAt( id, target ) { + getBoundingSphereAt( geometryId, target ) { - const active = this._active; - if ( active[ id ] === false ) { + if ( geometryId >= this._geometryCount ) { return null; } // compute bounding sphere - const bound = this._bounds[ id ]; + const bound = this._bounds[ geometryId ]; const sphere = bound.sphere; const geometry = this.geometry; if ( bound.sphereInitialized === false ) { sphere.makeEmpty(); - this.getBoundingBoxAt( id, _box ); + this.getBoundingBoxAt( geometryId, _box ); _box.getCenter( sphere.center ); const index = geometry.index; const position = geometry.attributes.position; - const drawRange = this._drawRanges[ id ]; + const drawRange = this._drawRanges[ geometryId ]; let maxRadiusSq = 0; for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { @@ -740,44 +720,42 @@ class BatchedMesh extends Mesh { } - setMatrixAt( geometryId, matrix ) { + setMatrixAt( instanceId, matrix ) { // @TODO: Map geometryId to index of the arrays because // optimize() can make geometryId mismatch the index - const active = this._active; + const drawInfo = this._drawInfo; const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { return this; } - matrix.toArray( matricesArray, geometryId * 16 ); + matrix.toArray( matricesArray, instanceId * 16 ); matricesTexture.needsUpdate = true; return this; } - getMatrixAt( geometryId, matrix ) { + getMatrixAt( instanceId, matrix ) { - const active = this._active; + const drawInfo = this._drawInfo; const matricesArray = this._matricesTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { return null; } - return matrix.fromArray( matricesArray, geometryId * 16 ); + return matrix.fromArray( matricesArray, instanceId * 16 ); } - setColorAt( geometryId, color ) { + setColorAt( instanceId, color ) { if ( this._colorsTexture === null ) { @@ -785,89 +763,79 @@ class BatchedMesh extends Mesh { } - // @TODO: Map geometryId to index of the arrays because - // optimize() can make geometryId mismatch the index + // @TODO: Map id to index of the arrays because + // optimize() can make id mismatch the index - const active = this._active; const colorsTexture = this._colorsTexture; const colorsArray = this._colorsTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + const drawInfo = this._drawInfo; + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { return this; } - color.toArray( colorsArray, geometryId * 4 ); + color.toArray( colorsArray, instanceId * 4 ); colorsTexture.needsUpdate = true; return this; } - getColorAt( geometryId, color ) { + getColorAt( instanceId, color ) { - const active = this._active; const colorsArray = this._colorsTexture.image.data; - const geometryCount = this._geometryCount; - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + const drawInfo = this._drawInfo; + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { return null; } - return color.fromArray( colorsArray, geometryId * 4 ); + return color.fromArray( colorsArray, instanceId * 4 ); } - setVisibleAt( geometryId, value ) { - - const visibility = this._visibility; - const active = this._active; - const geometryCount = this._geometryCount; + setVisibleAt( instanceId, value ) { // if the geometry is out of range, not active, or visibility state // does not change then return early + const drawInfo = this._drawInfo; if ( - geometryId >= geometryCount || - active[ geometryId ] === false || - visibility[ geometryId ] === value + instanceId >= drawInfo.length || + drawInfo[ instanceId ].active === false || + drawInfo[ instanceId ].visible === value ) { return this; } - visibility[ geometryId ] = value; + drawInfo[ instanceId ].visible = value; this._visibilityChanged = true; return this; } - getVisibleAt( geometryId ) { - - const visibility = this._visibility; - const active = this._active; - const geometryCount = this._geometryCount; + getVisibleAt( instanceId ) { // return early if the geometry is out of range or not active - if ( geometryId >= geometryCount || active[ geometryId ] === false ) { + const drawInfo = this._drawInfo; + if ( instanceId >= drawInfo.length || drawInfo[ instanceId ].active === false ) { return false; } - return visibility[ geometryId ]; + return drawInfo[ instanceId ].visible; } raycast( raycaster, intersects ) { - const visibility = this._visibility; - const active = this._active; + const drawInfo = this._drawInfo; const drawRanges = this._drawRanges; - const geometryCount = this._geometryCount; const matrixWorld = this.matrixWorld; const batchGeometry = this.geometry; @@ -887,21 +855,22 @@ class BatchedMesh extends Mesh { } - for ( let i = 0; i < geometryCount; i ++ ) { + for ( let i = 0, l = drawInfo.length; i < l; i ++ ) { - if ( ! visibility[ i ] || ! active[ i ] ) { + if ( ! drawInfo[ i ].visible || ! drawInfo[ i ].active ) { continue; } - const drawRange = drawRanges[ i ]; + const geometryId = drawInfo[ i ].geometryIndex; + const drawRange = drawRanges[ geometryId ]; _mesh.geometry.setDrawRange( drawRange.start, drawRange.count ); // ge the intersects this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); - this.getBoundingBoxAt( i, _mesh.geometry.boundingBox ); - this.getBoundingSphereAt( i, _mesh.geometry.boundingSphere ); + this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); + this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere ); _mesh.raycast( raycaster, _batchIntersects ); // add batch id to the intersects @@ -938,8 +907,7 @@ class BatchedMesh extends Mesh { this._drawRanges = source._drawRanges.map( range => ( { ...range } ) ); this._reservedRanges = source._reservedRanges.map( range => ( { ...range } ) ); - this._visibility = source._visibility.slice(); - this._active = source._active.slice(); + this._drawInfo = source._drawInfo.map( inf => ( { ...inf } ) ); this._bounds = source._bounds.map( bound => ( { boxInitialized: bound.boxInitialized, box: bound.box.clone(), @@ -948,7 +916,7 @@ class BatchedMesh extends Mesh { sphere: bound.sphere.clone() } ) ); - this._maxGeometryCount = source._maxGeometryCount; + this._maxInstanceCount = source._maxInstanceCount; this._maxVertexCount = source._maxVertexCount; this._maxIndexCount = source._maxIndexCount; @@ -979,6 +947,9 @@ class BatchedMesh extends Mesh { this._matricesTexture.dispose(); this._matricesTexture = null; + this._indirectTexture.dispose(); + this._indirectTexture = null; + if ( this._colorsTexture !== null ) { this._colorsTexture.dispose(); @@ -1005,12 +976,13 @@ class BatchedMesh extends Mesh { const index = geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; - const active = this._active; - const visibility = this._visibility; + const drawInfo = this._drawInfo; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const drawRanges = this._drawRanges; const perObjectFrustumCulled = this.perObjectFrustumCulled; + const indirectTexture = this._indirectTexture; + const indirectArray = indirectTexture.image.data; // prepare the frustum in the local frame if ( perObjectFrustumCulled ) { @@ -1033,13 +1005,15 @@ class BatchedMesh extends Mesh { _vector.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _invMatrixWorld ); _forward.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ).transformDirection( _invMatrixWorld ); - for ( let i = 0, l = visibility.length; i < l; i ++ ) { + for ( let i = 0, l = drawInfo.length; i < l; i ++ ) { + + if ( drawInfo[ i ].visible && drawInfo[ i ].active ) { - if ( visibility[ i ] && active[ i ] ) { + const geometryId = drawInfo[ i ].geometryIndex; // get the bounds in world space this.getMatrixAt( i, _matrix ); - this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix ); + this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); // determine whether the batched geometry is within the frustum let culled = false; @@ -1053,7 +1027,7 @@ class BatchedMesh extends Mesh { // get the distance from camera used for sorting const z = _temp.subVectors( _sphere.center, _vector ).dot( _forward ); - _renderList.push( drawRanges[ i ], z ); + _renderList.push( drawRanges[ geometryId ], z, i ); } @@ -1079,6 +1053,7 @@ class BatchedMesh extends Mesh { const item = list[ i ]; multiDrawStarts[ count ] = item.start * bytesPerElement; multiDrawCounts[ count ] = item.count; + indirectArray[ count ] = item.index; count ++; } @@ -1087,9 +1062,11 @@ class BatchedMesh extends Mesh { } else { - for ( let i = 0, l = visibility.length; i < l; i ++ ) { + for ( let i = 0, l = drawInfo.length; i < l; i ++ ) { + + if ( drawInfo[ i ].visible && drawInfo[ i ].active ) { - if ( visibility[ i ] && active[ i ] ) { + const geometryId = drawInfo[ i ].geometryIndex; // determine whether the batched geometry is within the frustum let culled = false; @@ -1097,16 +1074,17 @@ class BatchedMesh extends Mesh { // get the bounds in world space this.getMatrixAt( i, _matrix ); - this.getBoundingSphereAt( i, _sphere ).applyMatrix4( _matrix ); + this.getBoundingSphereAt( geometryId, _sphere ).applyMatrix4( _matrix ); culled = ! _frustum.intersectsSphere( _sphere ); } if ( ! culled ) { - const range = drawRanges[ i ]; + const range = drawRanges[ geometryId ]; multiDrawStarts[ count ] = range.start * bytesPerElement; multiDrawCounts[ count ] = range.count; + indirectArray[ count ] = i; count ++; } @@ -1117,6 +1095,7 @@ class BatchedMesh extends Mesh { } + indirectTexture.needsUpdate = true; this._multiDrawCount = count; this._visibilityChanged = false; diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index ca422d97caa424..86a49d7adfe9f7 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -861,7 +861,25 @@ class WebGLRenderer { } else { - renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { + + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; + const uniforms = properties.get( material ).currentProgram.getUniforms(); + for ( let i = 0; i < drawCount; i ++ ) { + + uniforms.setValue( _gl, '_gl_DrawID', i ); + renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); + + } + + } else { + + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + + } } @@ -2017,6 +2035,9 @@ class WebGLRenderer { p_uniforms.setOptional( _gl, object, 'batchingTexture' ); p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); + p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); + p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); + p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); if ( object._colorsTexture !== null ) { diff --git a/src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js b/src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js index 89c7829b61ab7f..5ae4c0ac38d660 100644 --- a/src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js +++ b/src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js @@ -1,7 +1,12 @@ export default /* glsl */` #ifdef USE_BATCHING - attribute float batchId; + #if ! defined( GL_ANGLE_multi_draw ) + #define gl_DrawID _gl_DrawID + uniform int _gl_DrawID; + #endif + uniform highp sampler2D batchingTexture; + uniform highp usampler2D batchingIdTexture; mat4 getBatchingMatrix( const in float i ) { int size = textureSize( batchingTexture, 0 ).x; @@ -15,6 +20,16 @@ export default /* glsl */` return mat4( v1, v2, v3, v4 ); } + + float getIndirectIndex( const in int i ) { + + int size = textureSize( batchingIdTexture, 0 ).x; + int x = i % size; + int y = i / size; + return float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r ); + + } + #endif #ifdef USE_BATCHING_COLOR diff --git a/src/renderers/shaders/ShaderChunk/batching_vertex.glsl.js b/src/renderers/shaders/ShaderChunk/batching_vertex.glsl.js index 6eb4aadad2c5ba..7364c8b9042fff 100644 --- a/src/renderers/shaders/ShaderChunk/batching_vertex.glsl.js +++ b/src/renderers/shaders/ShaderChunk/batching_vertex.glsl.js @@ -1,5 +1,5 @@ export default /* glsl */` #ifdef USE_BATCHING - mat4 batchingMatrix = getBatchingMatrix( batchId ); + mat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) ); #endif `; diff --git a/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js b/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js index daf7164e60cb79..42a26e56b38414 100644 --- a/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js +++ b/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js @@ -23,7 +23,7 @@ export default /* glsl */` #ifdef USE_BATCHING_COLOR - vec3 batchingColor = getBatchingColor( batchId ); + vec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) ); vColor.xyz *= batchingColor.xyz; diff --git a/src/renderers/webgl/WebGLBufferRenderer.js b/src/renderers/webgl/WebGLBufferRenderer.js index 29825eef2e27e4..1f5802a6dfd660 100644 --- a/src/renderers/webgl/WebGLBufferRenderer.js +++ b/src/renderers/webgl/WebGLBufferRenderer.js @@ -31,30 +31,17 @@ function WebGLBufferRenderer( gl, extensions, info ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - if ( extension === null ) { - - for ( let i = 0; i < drawCount; i ++ ) { + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - this.render( starts[ i ], counts[ i ] ); - - } - - } else { - - extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { - - elementCount += counts[ i ]; - - } - - info.update( elementCount, mode, 1 ); + elementCount += counts[ i ]; } + info.update( elementCount, mode, 1 ); + } function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { diff --git a/src/renderers/webgl/WebGLIndexedBufferRenderer.js b/src/renderers/webgl/WebGLIndexedBufferRenderer.js index 7ba8d2cf6e19dc..c6a1e8a59e4edc 100644 --- a/src/renderers/webgl/WebGLIndexedBufferRenderer.js +++ b/src/renderers/webgl/WebGLIndexedBufferRenderer.js @@ -40,29 +40,17 @@ function WebGLIndexedBufferRenderer( gl, extensions, info ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); - if ( extension === null ) { - - for ( let i = 0; i < drawCount; i ++ ) { - - this.render( starts[ i ] / bytesPerElement, counts[ i ] ); - - } - - } else { + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); + elementCount += counts[ i ]; - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { - - elementCount += counts[ i ]; - - } + } - info.update( elementCount, mode, 1 ); + info.update( elementCount, mode, 1 ); - } } diff --git a/src/renderers/webgl/WebGLPrograms.js b/src/renderers/webgl/WebGLPrograms.js index 82a19b766e4aa4..ade7725871c8a8 100644 --- a/src/renderers/webgl/WebGLPrograms.js +++ b/src/renderers/webgl/WebGLPrograms.js @@ -352,7 +352,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities index0AttributeName: material.index0AttributeName, extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), - extensionMultiDraw: HAS_EXTENSIONS && material.extensions.multiDraw === true && extensions.has( 'WEBGL_multi_draw' ), + extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ),