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' ),