Instanced mesh

인스턴스드 메시

Drawing thousands of objects that share one geometry in a single draw call.

Also known as: THREE.InstancedMeshGPU instancingDraw call batching
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(4.2, 3.6, 4.2);
camera.lookAt(0, 0, 0);
const dLight = new THREE.DirectionalLight(0xffffff, 1.3);
dLight.position.set(3, 5, 2);
scene.add(dLight);
scene.add(new THREE.AmbientLight(0x404060, 1.2));

const GRID = 18;
const COUNT = GRID * GRID;
const geo = new THREE.BoxGeometry(0.32, 0.32, 0.32);
const mat = new THREE.MeshStandardMaterial({ roughness: 0.45, metalness: 0.1 });
const mesh = new THREE.InstancedMesh(geo, mat, COUNT);
const dummy = new THREE.Object3D();
const color = new THREE.Color();
let i = 0;
for (let x = 0; x < GRID; x++) {
  for (let z = 0; z < GRID; z++) {
    const px = (x - (GRID - 1) / 2) * 0.42;
    const pz = (z - (GRID - 1) / 2) * 0.42;
    color.setHSL(0.58 + (px + pz) * 0.01, 0.7, 0.6);
    mesh.setColorAt(i, color);
    i++;
  }
}
scene.add(mesh);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  let idx = 0;
  for (let x = 0; x < GRID; x++) {
    for (let z = 0; z < GRID; z++) {
      const px = (x - (GRID - 1) / 2) * 0.42;
      const pz = (z - (GRID - 1) / 2) * 0.42;
      const h = Math.sin(time * 1.6 + (px + pz) * 1.4) * 0.35 + 0.4;
      dummy.position.set(px, h, pz);
      dummy.updateMatrix();
      mesh.setMatrixAt(idx, dummy.matrix);
      idx++;
    }
  }
  mesh.instanceMatrix.needsUpdate = true;
  mesh.rotation.y = time * 0.12;
  renderer.render(scene, camera);
});

Making 324 identical boxes with 324 separate new THREE.Mesh() calls means 324 draw calls, and draw calls are expensive on the CPU side more than the GPU. THREE.InstancedMesh(geometry, material, count) uploads the geometry and material to the GPU once, then draws every instance in one call using only an array of per-instance transform matrices (position, rotation, scale).

Set each instance’s transform with mesh.setMatrixAt(index, matrix), and flip mesh.instanceMatrix.needsUpdate = true whenever it changes so it gets re-uploaded to the GPU. To vary color per instance, use setColorAt(index, color) — it’s picked up automatically, no material flag needed.

The demo bobs an 18×18 grid of small cubes up and down, each on its own sine-wave phase. All 324 are drawn in a single draw call. If you have very many instances updating every frame (particles, say), even this CPU loop gets expensive — at that point, consider computing positions directly in the vertex shader instead (see gpu-particles).

When to use

Scenes with mass repetition of one shape — grass, trees, crowds, building windows.