인스턴스드 메시

Instanced mesh

같은 지오메트리를 가진 수천 개의 오브젝트를 단 한 번의 드로우콜로 그리는 기법.

다른 이름: 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);
});

같은 모양의 박스 324개를 각각 new THREE.Mesh()로 만들면 324번의 드로우콜이 발생하고, 드로우콜은 GPU보다 CPU 쪽에서 비쌉니다. THREE.InstancedMesh(geometry, material, count)는 지오메트리와 머티리얼을 한 번만 GPU에 올려두고, 인스턴스마다 변환 행렬(위치·회전·크기)만 배열로 넘겨 한 번에 그립니다.

각 인스턴스의 변환은 mesh.setMatrixAt(index, matrix)로 설정하고, 바뀔 때마다 mesh.instanceMatrix.needsUpdate = true를 켜줘야 GPU로 다시 업로드됩니다. 색도 인스턴스별로 다르게 하려면 setColorAt(index, color)를 씁니다 — material에 별도 플래그를 켤 필요 없이 자동으로 인식됩니다.

데모는 18×18 격자의 작은 큐브가 각자 다른 위상으로 사인파를 그리며 오르내립니다. 324개 전부가 단 하나의 드로우콜로 그려집니다. 인스턴스 개수가 아주 많고 자주 움직인다면(파티클 등) 이 CPU 루프 자체도 비용이 커지니, 그 경우엔 정점 셰이더에서 직접 위치를 계산하는 GPU 파티클 방식을 고려하세요.

언제 쓰나

풀·나무·군중·건물 창문처럼 같은 형태가 대량으로 반복되는 장면에.