Skip to content

Commit

Permalink
BatchedMesh: Add support for Instanced rendering (#1021)
Browse files Browse the repository at this point in the history
* BatchedMesh: Add support for Instanced rendering with sorting, frustum culling

* Update three.js

* Add examples

* Update patch

* Delete examples
  • Loading branch information
Methuselah96 authored Jun 18, 2024
1 parent d1cc690 commit 1792852
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 82 deletions.
60 changes: 33 additions & 27 deletions examples-testing/changes.patch
Original file line number Diff line number Diff line change
Expand Up @@ -6622,29 +6622,27 @@ index c9947c69..69bf22b4 100644
// Configure and create Draco decoder.
const dracoLoader = new DRACOLoader();
diff --git a/examples-testing/examples/webgl_loader_fbx.ts b/examples-testing/examples/webgl_loader_fbx.ts
index 9c044b13..cc34275c 100644
index 3b157a22..af4de6ac 100644
--- a/examples-testing/examples/webgl_loader_fbx.ts
+++ b/examples-testing/examples/webgl_loader_fbx.ts
@@ -6,10 +6,16 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
@@ -8,8 +8,14 @@ import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

const manager = new THREE.LoadingManager();

-let camera, scene, renderer, stats, object, loader, guiMorphsFolder;
-let mixer;
+let camera: THREE.PerspectiveCamera,
+ scene: THREE.Scene,
+ renderer: THREE.WebGLRenderer,
+ stats: Stats,
+ object: THREE.Group,
+ loader: FBXLoader,
+ guiMorphsFolder: GUI;
const clock = new THREE.Clock();

-let mixer;
+let mixer: THREE.AnimationMixer | null;

const params = {
asset: 'Samba Dancing',
@@ -87,13 +93,18 @@ function init() {
const clock = new THREE.Clock();

@@ -89,19 +95,22 @@ function init() {
guiMorphsFolder = gui.addFolder('Morphs').hide();
}

Expand All @@ -6653,21 +6651,27 @@ index 9c044b13..cc34275c 100644
loader.load('models/fbx/' + asset + '.fbx', function (group) {
if (object) {
object.traverse(function (child) {
- if (child.material) child.material.dispose();
- if (child.material && child.material.map) child.material.map.dispose();
- if (child.material) {
- const materials = Array.isArray(child.material) ? child.material : [child.material];
+ if ((child as THREE.Mesh).material) {
+ const materials = Array.isArray((child as THREE.Mesh).material)
+ ? (child as THREE.Mesh<THREE.BufferGeometry, THREE.Material[]>).material
+ : [(child as THREE.Mesh).material];
materials.forEach(material => {
- if (material.map) material.map.dispose();
- material.dispose();
+ if ((material as THREE.MeshPhongMaterial).map)
+ (material as THREE.MeshPhongMaterial).map!.dispose();
+ (material as THREE.MeshPhongMaterial).dispose();
});
}

- if (child.geometry) child.geometry.dispose();
+ if ((child as THREE.Mesh<THREE.BufferGeometry, THREE.Material>).material)
+ (child as THREE.Mesh<THREE.BufferGeometry, THREE.Material>).material.dispose();
+ if (
+ (child as THREE.Mesh<THREE.BufferGeometry, THREE.MeshPhongMaterial>).material &&
+ (child as THREE.Mesh<THREE.BufferGeometry, THREE.MeshPhongMaterial>).material.map
+ )
+ (child as THREE.Mesh<THREE.BufferGeometry, THREE.MeshPhongMaterial>).material.map!.dispose();
+ if ((child as THREE.Mesh).geometry) (child as THREE.Mesh).geometry.dispose();
});

scene.remove(object);
@@ -116,15 +127,21 @@ function loadAsset(asset) {
@@ -124,15 +133,21 @@ function loadAsset(asset) {
guiMorphsFolder.hide();

object.traverse(function (child) {
Expand Down Expand Up @@ -9100,7 +9104,7 @@ index 99be247d..2ff2bca4 100644
const spherical = new THREE.Spherical();
const rotationMatrix = new THREE.Matrix4();
diff --git a/examples-testing/examples/webgl_mesh_batch.ts b/examples-testing/examples/webgl_mesh_batch.ts
index 5c70b6ce..7ddd3013 100644
index 1d6aba4c..ee612f75 100644
--- a/examples-testing/examples/webgl_mesh_batch.ts
+++ b/examples-testing/examples/webgl_mesh_batch.ts
@@ -4,12 +4,12 @@ import Stats from 'three/addons/libs/stats.module.js';
Expand Down Expand Up @@ -9153,7 +9157,7 @@ index 5c70b6ce..7ddd3013 100644
}
}
}
@@ -213,14 +213,18 @@ function init() {
@@ -219,14 +219,18 @@ function init() {

//

Expand All @@ -9167,15 +9171,16 @@ index 5c70b6ce..7ddd3013 100644
- this._options = this._options || {
+ (this as BatchedMeshWithOptions)._options = (this as BatchedMeshWithOptions)._options || {
get: el => el.z,
aux: new Array(this.maxGeometryCount),
- aux: new Array(this.maxGeometryCount),
+ aux: new Array(this.maxInstanceCount),
};

- const options = this._options;
+ const options = (this as BatchedMeshWithOptions)._options!;
options.reversed = this.material.transparent;

let minZ = Infinity;
@@ -270,9 +274,9 @@ function animateMeshes() {
@@ -276,9 +280,9 @@ function animateMeshes() {
const rotationMatrix = mesh.userData.rotationSpeeds[i];
const id = ids[i];

Expand All @@ -9187,7 +9192,7 @@ index 5c70b6ce..7ddd3013 100644
}
} else {
for (let i = 0; i < loopNum; i++) {
@@ -289,10 +293,10 @@ function animateMeshes() {
@@ -295,10 +299,10 @@ function animateMeshes() {
}

function render() {
Expand Down Expand Up @@ -13860,7 +13865,7 @@ index fbbabfca..a1a82bd3 100644
}

diff --git a/examples-testing/examples/webgpu_mesh_batch.ts b/examples-testing/examples/webgpu_mesh_batch.ts
index 53166211..48fb5207 100644
index 53166211..ada965e7 100644
--- a/examples-testing/examples/webgpu_mesh_batch.ts
+++ b/examples-testing/examples/webgpu_mesh_batch.ts
@@ -4,15 +4,15 @@ import Stats from 'stats-gl';
Expand Down Expand Up @@ -13938,7 +13943,8 @@ index 53166211..48fb5207 100644
- this._options = this._options || {
+ (this as BatchedMeshWithOptions)._options = (this as BatchedMeshWithOptions)._options || {
get: el => el.z,
aux: new Array(this.maxGeometryCount),
- aux: new Array(this.maxGeometryCount),
+ aux: new Array(this.maxInstanceCount),
};

- const options = this._options;
Expand Down
2 changes: 1 addition & 1 deletion three.js
Submodule three.js updated 46 files
+13 −1 build/three.cjs
+13 −1 build/three.module.js
+1 −1 build/three.module.min.js
+3 −3 docs/api/ar/materials/RawShaderMaterial.html
+1 −1 docs/api/ar/renderers/WebGLArrayRenderTarget.html
+3 −3 docs/api/en/materials/RawShaderMaterial.html
+1 −1 docs/api/en/renderers/WebGLArrayRenderTarget.html
+4 −4 docs/api/en/textures/Data3DTexture.html
+2 −2 docs/api/en/textures/DataArrayTexture.html
+3 −3 docs/api/fr/materials/RawShaderMaterial.html
+3 −3 docs/api/it/materials/RawShaderMaterial.html
+1 −1 docs/api/it/renderers/WebGLArrayRenderTarget.html
+1 −1 docs/api/it/textures/Data3DTexture.html
+1 −1 docs/api/it/textures/DataArrayTexture.html
+3 −3 docs/api/zh/materials/RawShaderMaterial.html
+1 −1 docs/api/zh/renderers/WebGLArrayRenderTarget.html
+1 −1 docs/api/zh/textures/Data3DTexture.html
+2 −2 docs/api/zh/textures/DataArrayTexture.html
+23 −45 examples/jsm/loaders/FBXLoader.js
+1 −1 examples/jsm/nodes/Nodes.js
+3 −0 examples/jsm/nodes/math/MathNode.js
+ examples/screenshots/webgl_multisampled_renderbuffers.jpg
+ examples/screenshots/webgl_volume_instancing.jpg
+ examples/screenshots/webgpu_lights_custom.jpg
+ examples/screenshots/webgpu_performance_renderbundle.jpg
+ examples/screenshots/webgpu_pmrem_equirectangular.jpg
+ examples/screenshots/webgpu_pmrem_scene.jpg
+ examples/screenshots/webgpu_postprocessing_anamorphic.jpg
+ examples/screenshots/webgpu_reflection.jpg
+ examples/screenshots/webgpu_rtt.jpg
+ examples/screenshots/webgpu_skinning.jpg
+ examples/screenshots/webgpu_skinning_instancing.jpg
+ examples/screenshots/webgpu_textures_anisotropy.jpg
+ examples/screenshots/webxr_xr_dragging_custom_depth.jpg
+17 −5 examples/webgl_loader_fbx.html
+9 −3 examples/webgl_mesh_batch.html
+1 −1 src/constants.js
+164 −185 src/objects/BatchedMesh.js
+22 −1 src/renderers/WebGLRenderer.js
+16 −1 src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js
+1 −1 src/renderers/shaders/ShaderChunk/batching_vertex.glsl.js
+1 −1 src/renderers/shaders/ShaderChunk/color_vertex.glsl.js
+6 −19 src/renderers/webgl/WebGLBufferRenderer.js
+6 −18 src/renderers/webgl/WebGLIndexedBufferRenderer.js
+1 −1 src/renderers/webgl/WebGLPrograms.js
+2 −3 test/e2e/puppeteer.js
112 changes: 58 additions & 54 deletions types/three/src/objects/BatchedMesh.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,36 @@ import { Mesh } from "./Mesh.js";

/**
* A special version of {@link Mesh} with multi draw batch rendering support. Use {@link BatchedMesh} if you have to
* render a large number of objects with the same material but with different world transformations and geometry. The
* usage of {@link BatchedMesh} will help you to reduce the number of draw calls and thus improve the overall rendering
* render a large number of objects with the same material but with different world transformations. The usage of
* {@link BatchedMesh} will help you to reduce the number of draw calls and thus improve the overall rendering
* performance in your application.
*
* If the {@link https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_multi_draw WEBGL_multi_draw extension} is not
* supported then a less performant callback is used.
* supported then a less performant fallback is used.
*
* @example
* const box = new THREE.BoxGeometry( 1, 1, 1 );
* const sphere = new THREE.BoxGeometry( 1, 1, 1 );
* const sphere = new THREE.SphereGeometry( 1, 12, 12 );
* const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
*
* // initialize and add geometries into the batched mesh
* const batchedMesh = new BatchedMesh( 10, 5000, 10000, material );
* const boxId = batchedMesh.addGeometry( box );
* const sphereId = batchedMesh.addGeometry( sphere );
* const boxGeometryId = batchedMesh.addGeometry( box );
* const sphereGeometryId = batchedMesh.addGeometry( sphere );
*
* // create instances of those geometries
* const boxInstancedId1 = batchedMesh.addInstance( boxGeometryId );
* const boxInstancedId2 = batchedMesh.addInstance( boxGeometryId );
*
* const sphereInstancedId1 = batchedMesh.addInstance( sphereGeometryId );
* const sphereInstancedId2 = batchedMesh.addInstance( sphereGeometryId );
*
* // position the geometries
* batchedMesh.setMatrixAt( boxId, boxMatrix );
* batchedMesh.setMatrixAt( sphereId, sphereMatrix );
* batchedMesh.setMatrixAt( boxInstancedId1, boxMatrix1 );
* batchedMesh.setMatrixAt( boxInstancedId2, boxMatrix2 );
*
* batchedMesh.setMatrixAt( sphereInstancedId1, sphereMatrix1 );
* batchedMesh.setMatrixAt( sphereInstancedId2, sphereMatrix2 );
*
* scene.add( batchedMesh );
*
Expand Down Expand Up @@ -68,20 +78,20 @@ declare class BatchedMesh extends Mesh<BufferGeometry, Material> {
/**
* The maximum number of individual geometries that can be stored in the {@link BatchedMesh}. Read only.
*/
get maxGeometryCount(): number;
get maxInstanceCount(): number;

/**
* Read-only flag to check if a given object is of type {@link BatchedMesh}.
*/
isBatchedMesh: true;
readonly isBatchedMesh: true;

/**
* @param maxGeometryCount the max number of individual geometries planned to be added.
* @param maxInstanceCount the max number of individual geometries planned to be added.
* @param maxVertexCount the max number of vertices to be used by all geometries.
* @param maxIndexCount the max number of indices to be used by all geometries.
* @param material an instance of [page:Material]. Default is a new {@link MeshBasicMaterial}.
* @param material an instance of {@link Material}. Default is a new {@link MeshBasicMaterial}.
*/
constructor(maxGeometryCount: number, maxVertexCount: number, maxIndexCount?: number, material?: Material);
constructor(maxInstanceCount: number, maxVertexCount: number, maxIndexCount?: number, material?: Material);

/**
* Computes the bounding box, updating {@link .boundingBox} attribute.
Expand All @@ -102,54 +112,55 @@ declare class BatchedMesh extends Mesh<BufferGeometry, Material> {
dispose(): this;

/**
* Takes a sort a function that is run before render. The function takes a list of items to sort and a camera. The
* objects in the list include a "z" field to perform a depth-ordered sort with.
* Takes a sort a function that is run before render. The function takes a list of instances to sort and a camera.
* The objects in the list include a "z" field to perform a depth-ordered sort with.
*/
setCustomSort(
func: ((this: this, list: Array<{ start: number; count: number; z: number }>, camera: Camera) => void) | null,
sortFunction:
| ((this: this, list: Array<{ start: number; count: number; z: number }>, camera: Camera) => void)
| null,
): this;

/**
* Get the color of the defined geometry.
* @param index The index of a geometry. Values have to be in the range [0, count].
* @param color This color object will be set to the color of the defined geometry.
* @param instanceId The id of an instance to get the color of.
* @param target The target object to copy the color in to.
*/
getColorAt(index: number, color: Color): void;
getColorAt(instanceId: number, target: Color): void;

/**
* Get the local transformation matrix of the defined instance.
* @param index The index of an instance. Values have to be in the range [0, count].
* @param matrix This 4x4 matrix will be set to the local transformation matrix of the defined instance.
* @param instanceId The id of an instance to get the matrix of.
* @param target This 4x4 matrix will be set to the local transformation matrix of the defined instance.
*/
getMatrixAt(index: number, matrix: Matrix4): Matrix4;
getMatrixAt(instanceId: number, target: Matrix4): Matrix4;

/**
* Get whether the given instance is marked as "visible" or not.
* @param index The index of an instance. Values have to be in the range [0, count].
* @param instanceId The id of an instance to get the visibility state of.
*/
getVisibleAt(index: number): boolean;
getVisibleAt(instanceId: number): boolean;

/**
* Sets the given color to the defined geometry.
* @param index The index of a geometry. Values have to be in the range [0, count].
* @param color The color of a single geometry.
* Sets the given color to the defined geometry instance.
* @param instanceId The id of the instance to set the color of.
* @param color The color to set the instance to.
*/
setColorAt(index: number, color: Color): void;
setColorAt(instanceId: number, color: Color): void;

/**
* Sets the given local transformation matrix to the defined instance. Make sure you set {@link .instanceMatrix}
* {@link BufferAttribute.needsUpdate} to true after updating all the matrices.
* @param index The index of an instance. Values have to be in the range [0, count].
* Sets the given local transformation matrix to the defined instance.
* @param instanceId The id of an instance to set the matrix of.
* @param matrix A 4x4 matrix representing the local transformation of a single instance.
*/
setMatrixAt(index: number, matrix: Matrix4): this;
setMatrixAt(instanceId: number, matrix: Matrix4): this;

/**
* Sets the visibility of the object at the given index.
* @param index The index of an instance. Values have to be in the range [0, count].
* Sets the visibility of the instance at the given index.
* @param instanceId The id of the instance to set the visibility of.
* @param visible A boolean value indicating the visibility state.
*/
setVisibleAt(index: number, visible: boolean): this;
setVisibleAt(instanceId: number, visible: boolean): this;

/**
* Adds the given geometry to the {@link BatchedMesh} and returns the associated index referring to it.
Expand All @@ -164,30 +175,23 @@ declare class BatchedMesh extends Mesh<BufferGeometry, Material> {
addGeometry(geometry: BufferGeometry, reservedVertexRange?: number, reservedIndexRange?: number): number;

/**
* Replaces the geometry at `index` with the provided geometry. Throws an error if there is not enough space
* reserved for geometry at the index.
* @param index Which geometry index to replace with this geometry.
* @param geometry The geometry to substitute at the given geometry index.
* Adds a new instance to the {@link BatchedMesh} using the geometry of the given geometryId and returns a new id
* referring to the new instance to be used by other functions.
* @param geometryId The id of a previously added geometry via "addGeometry" to add into the {@link BatchedMesh} to
* render.
*/
setGeometryAt(index: number, geometry: BufferGeometry): number;
addInstance(geometryId: number): number;

/**
* Gets the instance count of the geometry at `index`. Returns `null` if instance counts are not configured.
* @param index The index of an instance. Values have to be in the range [0, count].
* Replaces the geometry at `geometryId` with the provided geometry. Throws an error if there is not enough space
* reserved for geometry. Calling this will change all instances that are rendering that geometry.
* @param geometryId Which geometry id to replace with this geometry.
* @param geometry The geometry to substitute at the given geometry id.
*/
getInstanceCountAt(index: number): number | null;

/**
* Sets an instance count of the geometry at `index`.
* @param index Which geometry index to configure an instance count for.
* @param instanceCount The number of instances to render of the given geometry index.
*/
setInstanceCountAt(index: number, instanceCount: number): number;

deleteGeometry(index: number): this;
setGeometryAt(geometryId: number, geometry: BufferGeometry): number;

getBoundingBoxAt(index: number, target: Box3): Box3 | null;
getBoundingSphereAt(index: number, target: Sphere): Sphere | null;
getBoundingBoxAt(geometryId: number, target: Box3): Box3 | null;
getBoundingSphereAt(geometryId: number, target: Sphere): Sphere | null;
}

export { BatchedMesh };

0 comments on commit 1792852

Please sign in to comment.