Instanced mesh
Enhanced InstancedMesh with frustum culling, fast raycasting (using a BVH), sorting, visibility, LOD, skinning and more.
Simplify your three.js application development with three.ez! three.ez/batched-mesh-extensions - BatchedMesh extension methods and enhancements for better performance and usability The project is written primarily in TypeScript, distributed under the MIT License license, first published in 2024. Key topics include: bvh, frustum-culling, instancedmesh2, instances, lod.
<img src="public/banner.png" alt="banner" /> <br>
</div>InstancedMesh2 is an alternative version of InstancedMesh with enhanced features for performance and usability.
tsconst myInstancedMesh = new InstancedMesh2(geometry, material); myInstancedMesh.addInstances(count, (obj, index) => { obj.position.x = index; });
- Dynamic capacity: add or remove instances seamlessly.
- Object3D-like instances: use instances like
Object3Dwith transforms and custom data. - Per-instance frustum culling: skip rendering for out-of-view instances.
- Spatial indexing (dynamic BVH): speed up raycasting and frustum culling.
- Sorting: reduce overdraw and manage transparent objects efficiently.
- Per-instance visibility: toggle visibility for each instance individually.
- Per-instance opacity: set opacity for each instance individually.
- Per-instance uniforms: assign unique shader data to individual instances.
- Level of Detail (LOD): dynamically adjust instance detail based on distance.
- Shadow LOD: optimize shadow rendering with lower detail for distant instances.
- Skinning: apply skeletal animations to instances for more complex and dynamic movements.
๐งโ๐ป Live Examples
Vanilla
- <img src="public/js.png" alt="js" width="16" /> Dynamic adding with BVH (thanks to Saumac)
Using three.ez/main
- <img src="public/ts.png" alt="ts" width="16" /> 1kk static trees
- <img src="public/ts.png" alt="ts" width="16" /> Instances array dynamic
- <img src="public/ts.png" alt="ts" width="16" /> Sorting
- <img src="public/ts.png" alt="ts" width="16" /> Uniforms per instance
- <img src="public/ts.png" alt="ts" width="16" /> Dynamic BVH (no vite)
- <img src="public/ts.png" alt="ts" width="16" /> Fast raycasting
- <img src="public/ts.png" alt="ts" width="16" /> LOD
- <img src="public/ts.png" alt="ts" width="16" /> Shadow LOD
- <img src="public/ts.png" alt="js" width="16" /> Skinning 3k instances
- <img src="public/js.png" alt="js" width="16" /> Dynamic adding with BVH
- <img src="public/js.png" alt="js" width="16" /> Skinning
Using other libraries
- Threlte
- <img src="public/js.png" alt="js" width="16" /> Tres.js (thanks to JaimeTorrealba)
- <img src="public/ts.png" alt="ts" width="16" /> React-three-fiber (thanks to Saumac)
- <img src="public/js.png" alt="js" width="16" /> React-three-fiber (thanks to Lunakepio)
โ Need help?
Join us on Discord or open an issue on GitHub.
โญ Like it?
If you like this project, please leave a star. Thank you! โค๏ธ
๐ Documentation
The documentation is available here.
โฌ๏ธ Installation
You can install it via npm using the following command:
bashnpm install @three.ez/instanced-mesh
Or you can import it from CDN:
html<script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js", "three/addons/": "https://cdn.jsdelivr.net/npm/three/examples/jsm/", "@three.ez/instanced-mesh": "https://cdn.jsdelivr.net/npm/@three.ez/instanced-mesh/build/index.js", "bvh.js": "https://cdn.jsdelivr.net/npm/bvh.js/build/index.js" } } </script>
๐ Features
Dynamic capacity
Manage a dynamic number of instances, automatically expanding the data buffers as needed to accommodate additional instances. <br>
If not specified, capacity is 1000. <br>
tsconst myInstancedMesh = new InstancedMesh2(geometry, material, { capacity: count }); myInstancedMesh.addInstances(count, (obj, index) => { ... }); // add instances and expand buffer if necessary myInstancedMesh.removeInstances(id0, id1, ...); myInstancedMesh.clearInstances(); // remove all instances
Object3D-like instances
It's possible to create an array of InstancedEntity (Object3D-like) in order to easily manipulate instances, using more memory.
tsconst myInstancedMesh = new InstancedMesh2(geometry, material, { createEntities: true }); myInstancedMesh.instances[0].customData = {}; myInstancedMesh.instances[0].position.random(); myInstancedMesh.instances[0].rotateX(Math.PI); myInstancedMesh.instances[0].updateMatrix(); // necessary after transformations
Per-instance frustum culling
Avoiding rendering objects outside the camera frustum can drastically improve performance (especially for complex geometries). <br>
Frustum culling by default is performed by iterating all instances, but it is possible to speed up this process by creating a spatial indexing data structure (BVH). <br>
By default perObjectFrustumCulled is true.
Spatial indexing (dynamic BVH)
To speed up raycasting and frustum culling, a spatial indexing data structure can be created to contain the boundingBoxes of all instances. <br>
This works very well if the instances are mostly static (updating a BVH can be expensive) and scattered in world space. <br>
Setting a margin makes BVH updating faster, but may make raycasting and frustum culling slightly slower.
tsmyInstancedMesh.computeBVH({ margin: 0 }); // margin is optional
Sorting
Sorting can be used to decrease overdraw and render transparent objects. <br>
It's possible to improve sort performance adding a customSort, like built-in createRadixSort.
By default sortObjects is false. <br>
tsimport { createRadixSort } from '@three.ez/instanced-mesh'; myInstancedMesh.sortObjects = true; myInstancedMesh.customSort = createRadixSort(myInstancedMesh);
Per-instance visibility
Set the visibility status of each instance.
tsmyInstancedMesh.setVisibilityAt(index, false); myInstancedMesh.instances[0].visible = false; // if instances array is created
Per-instance opacity
Set the opacity of each instance. It's recommended to enable instances sorting and disable the depthWriting of the material.
tsmyInstancedMesh.setOpacityAt(index, 0.5); myInstancedMesh.instances[0].opacity = 0.5; // if instances array is created
Per-instance uniforms
Assign unique shader uniforms to each instance, working with every materials.
tsmyInstancedMesh.initUniformsPerInstance({ fragment: { metalness: 'float', roughness: 'float', emissive: 'vec3' } }); myInstancedMesh.setUniformAt(index, 'metalness', 0.5); myInstancedMesh.instances[0].setUniform('emissive', new Color('white')); // if instances array is created
Level of Detail (LOD)
Improve rendering performance by dynamically adjusting the detail level of instances based on their distance from the camera. <br>
Use simplified geometries for distant objects to optimize resources.
tsmyInstancedMesh.addLOD(geometryMid, material, 50); myInstancedMesh.addLOD(geometryLow, material, 200);
Shadow LOD
Optimize shadow rendering by reducing the detail level of instances casting shadows based on their distance from the camera.
tsmyInstancedMesh.addShadowLOD(geometryMid); myInstancedMesh.addShadowLOD(geometryLow, 100);
Skinning
Apply skeletal animations to instances for more complex and dynamic movements.
tsmyInstancedMesh.initSkeleton(skeleton); mixer.update(time); myInstancedMesh.setBonesAt(index);
Raycasting tips
If you are not using a BVH, you can set the raycastOnlyFrustum property to true to avoid iterating over all instances.
It's recommended to use three-mesh-bvh to create a geometry BVH.
๐ค Special thanks to
๐ References
Contributors
Showing top 11 contributors by commit count.
