InstancedBufferGeometry vs InstancedMesh in Three.js
This article explains the differences between
InstancedBufferGeometry and InstancedMesh in
Three.js, helping you understand when and how to use each approach.
While both techniques are designed to optimize performance by rendering
thousands of similar objects in a single draw call, they operate at
different levels of abstraction and offer different degrees of control
over your 3D scenes.
Understanding GPU Instancing
In 3D graphics, rendering thousands of individual objects (like grass blades, trees, or particles) individually can quickly bottleneck the CPU due to excessive draw calls. GPU instancing solves this by sending the geometry data to the GPU once and then drawing it multiple times with different parameters in a single draw call.
Three.js provides two primary ways to achieve this:
InstancedMesh and InstancedBufferGeometry.
What is InstancedMesh?
InstancedMesh is a high-level, user-friendly class
introduced in Three.js to make instancing easy to implement. It is a
direct extension of the standard Mesh class.
With InstancedMesh, you define a single base geometry
and a single material. You then define the number of instances you want.
To manipulate individual instances, you use helper methods to modify
their transformation matrices (position, rotation, and scale) and
colors.
Key Features of InstancedMesh:
- Out-of-the-box support: Works seamlessly with
built-in Three.js materials (like
MeshBasicMaterial,MeshStandardMaterial, etc.). - Matrix manipulation: You set instance
transformations using
.setMatrixAt(index, matrix)and retrieve them with.getMatrixAt(index, matrix). - Color support: You can easily change individual
instance colors using
.setColorAt(index, color). - Raycasting: It has built-in support for raycasting, allowing you to easily detect mouse hovers or clicks on individual instances.
What is InstancedBufferGeometry?
InstancedBufferGeometry is a low-level API that extends
BufferGeometry. It allows you to define custom instanced
attributes using InstancedBufferAttribute.
Unlike InstancedMesh, which handles the transformation
math behind the scenes, InstancedBufferGeometry requires
you to write custom shaders (GLSL) using ShaderMaterial or
customize existing materials using onBeforeCompile. You
must manually write the vertex shader code to read your custom
attributes and position each instance on the screen.
Key Features of InstancedBufferGeometry:
- Custom Attributes: You can pass any arbitrary data per instance to the GPU, such as custom texture coordinates (UVs), velocities, custom animation phases, or health bars.
- Shader Control: Highly flexible because you write the GLSL code that dictates how the GPU interprets the instance data.
- Lightweight: Does not require the overhead of carrying full 4x4 transformation matrices if you only need to pass simple 3D vectors for positions.
Key Differences
| Feature | InstancedMesh | InstancedBufferGeometry |
|---|---|---|
| Level of Abstraction | High-level (Easy to use) | Low-level (Complex) |
| Material Support | Works with all default materials | Usually requires custom
ShaderMaterial |
| Custom Attributes | Limited to transform matrices and colors | Unlimited custom attributes |
| Raycasting Support | Built-in | Requires manual implementation |
| Learning Curve | Low | High (requires GLSL knowledge) |
When to Use Which?
Use InstancedMesh when:
- You want to render static or simple moving objects like forests, crowds, asteroid fields, or buildings.
- You want to use standard Three.js lighting, shadows, and materials without writing custom GLSL shaders.
- You need to detect user interaction (clicking or hovering) on individual instances using raycasting.
Use InstancedBufferGeometry when:
- You are building complex particle systems where particles have unique properties (like life-cycle, velocity, or size) calculated directly on the GPU.
- You need custom vertex shader animations, such as wind blowing through a field of grass where each blade bends at a different phase.
- You need to optimize memory to the absolute limit by passing custom, lightweight attributes instead of full transform matrices.