InstancedMesh in React Three Fiber
Every mesh in a scene costs one draw call. When you need many copies of the same shape, an instancedMesh draws all of them at once and you position each copy yourself.
All React Three Fiber examples React Three Fiber: usage and stats
How it works
- Why instancing. The React Three Fiber performance guide treats 1,000 draw calls as the hard maximum and a few hundred as the target, and points to instancing for repeated objects. Here 2,500 cubes are a single draw call. [1]
- Create the instanced mesh.
<instancedMesh args={[undefined, undefined, COUNT]}>passes the constructor arguments (geometry, material, count). Leaving the first two undefined lets the child geometry and material attach as usual. [2] - Move every instance in useFrame. A reusable
Object3D("dummy") gets a position and scale,updateMatrix()builds its matrix, andsetMatrixAt(i, dummy.matrix)copies it into instancei.setColorAtdoes the same for colours. [2] - Flag the buffers. After the loop,
instanceMatrix.needsUpdate = true(andinstanceColor.needsUpdate) tells three.js to upload the new values to the GPU on this frame. [2]
Run and edit it
It runs in your browser, in a sandboxed frame that cannot see this site; the packages load from esm.sh only after you press Run.
import { Canvas, useFrame } from '@react-three/fiber'
import { useMemo, useRef } from 'react'
import * as THREE from 'three'
const SIDE = 50
const COUNT = SIDE * SIDE
function Wave() {
const ref = useRef()
// reused every frame: no new objects inside the loop
const dummy = useMemo(() => new THREE.Object3D(), [])
const color = useMemo(() => new THREE.Color(), [])
useFrame(({ clock }) => {
const t = clock.elapsedTime
let i = 0
for (let x = 0; x < SIDE; x++) {
for (let z = 0; z < SIDE; z++) {
const d = Math.hypot(x - SIDE / 2, z - SIDE / 2)
const y = Math.sin(d * 0.35 - t * 2) * 0.8
dummy.position.set(x - SIDE / 2, y, z - SIDE / 2)
dummy.scale.setScalar(0.8)
dummy.updateMatrix()
ref.current.setMatrixAt(i, dummy.matrix)
ref.current.setColorAt(i, color.setHSL(0.58 + y * 0.08, 0.7, 0.55))
i++
}
}
ref.current.instanceMatrix.needsUpdate = true
ref.current.instanceColor.needsUpdate = true
})
return (
<instancedMesh ref={ref} args={[undefined, undefined, COUNT]}>
<boxGeometry args={[0.9, 0.9, 0.9]} />
<meshStandardMaterial />
</instancedMesh>
)
}
export default function App() {
return (
<Canvas camera={{ position: [42, 36, 42], fov: 40 }}>
<color attach="background" args={['#111']} />
<ambientLight intensity={0.6} />
<directionalLight position={[10, 20, 5]} intensity={2.5} />
<Wave />
</Canvas>
)
}
2,500 cubes drawn in one draw call: one instancedMesh, animated in useFrame by changing each instance's matrix. Code written by AgentGid [1].
2,500 cubes drawn in one draw call: one instancedMesh, animated in useFrame by changing each instance's matrix. Code written by AgentGid [1].Checked Oct 12, 2026: it starts without errors in Chromium with React Three Fiber 9.8.1, three.js 0.186.1, Drei 10.7.9, React 19.3.0. Needs WebGL.
More React Three Fiber examples
Shadows in React Three Fiber useFrame in React Three Fiber onClick and pointer events in React Three Fiber OrbitControls in React Three Fiber
Sources
Explanations written by AgentGid from the official docs; the numbers in brackets point here. The code marked "written by AgentGid" is ours and free to copy.