---
id: wiki-2026-0508-instancedmesh-사용-시-드로우-콜-최적화의-한계
title: InstancedMesh 사용 시 드로우 콜 최적화의 한계점 사례 연구
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [InstancedMesh Limits, Instancing Limits]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [graphics, three-js, performance, instancing]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: JavaScript/GLSL
  framework: Three.js r170+/WebGL2/WebGPU
---

# InstancedMesh 사용 시 드로우 콜 최적화의 한계점 사례 연구

## 매 한 줄
> **"매 InstancedMesh 매 1 draw call로 N copies — but 매 frustum culling, material variation, animation, picking 의 cost가 instance 수에 따라 explode."**. 매 naive 사용 시 draw call 매 줄어들어도 GPU vertex/fragment 매 burden, CPU matrix update 매 bottleneck. 매 production 매 LOD + spatial partition + GPU culling 매 결합.

## 매 핵심

### 매 한계 list
- **No per-instance frustum culling**: 매 single bounding sphere → 매 모든 instance가 frustum 안에 있다고 GPU가 가정.
- **No per-instance material**: 매 same material → color/texture variation 매 instanceColor / instance attribute 의 manual.
- **Animation cost**: 매 instance마다 matrix update → CPU bound at 10k+.
- **Picking 어려움**: raycaster 매 instance index 매 별도 처리.
- **Memory**: 16 floats × N instances = 매 1M instances → 64MB matrix buffer.
- **Shadow map**: 매 light 마다 또 한 번 instanced draw — culling 없으면 shadow waste.

### 매 case: 100k cubes
- Naive InstancedMesh: 매 1 draw call, GPU 60fps but matrix update 60ms/frame on CPU.
- Static (`setMatrixAt` once): 매 GPU bound, fillrate 매 issue → LOD 필요.
- Dynamic: 매 매 frame matrix update → useDynamicDrawUsage + partial updates.

### 매 응용 (해결 전략)
1. **GPU instancing + GPU culling**: compute shader 매 frustum check, 매 indirect draw.
2. **Spatial partitioning**: octree / BVH로 매 chunk 단위 InstancedMesh.
3. **LOD groups**: distance 별 다른 InstancedMesh (high/med/low/billboard).
4. **BatchedMesh (Three.js r170+)**: 매 different geometries를 single draw call.
5. **Hierarchical LOD + impostor**: 매 far away는 single quad billboard.

## 💻 패턴

### Frustum culling 수동 (Three.js)
```javascript
const frustum = new THREE.Frustum();
const m = new THREE.Matrix4();
m.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(m);

const dummy = new THREE.Object3D();
const sphere = new THREE.Sphere(new THREE.Vector3(), boundingRadius);
let visibleCount = 0;
for (let i = 0; i < totalInstances; i++) {
    sphere.center.copy(positions[i]);
    if (frustum.intersectsSphere(sphere)) {
        dummy.position.copy(positions[i]);
        dummy.updateMatrix();
        instancedMesh.setMatrixAt(visibleCount++, dummy.matrix);
    }
}
instancedMesh.count = visibleCount;
instancedMesh.instanceMatrix.needsUpdate = true;
```

### Per-instance color
```javascript
const mesh = new THREE.InstancedMesh(geo, mat, N);
const color = new THREE.Color();
for (let i = 0; i < N; i++) {
    color.setHSL(i / N, 0.7, 0.5);
    mesh.setColorAt(i, color);
}
mesh.instanceColor.needsUpdate = true;
// shader auto: gl_InstanceID → vInstanceColor
```

### Dynamic draw usage (partial updates)
```javascript
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
// only update changed instances
mesh.setMatrixAt(idx, newMatrix);
mesh.instanceMatrix.updateRange = { offset: idx * 16, count: 16 };
mesh.instanceMatrix.needsUpdate = true;
```

### Chunked InstancedMesh (spatial bucket)
```javascript
class ChunkedInstances {
    constructor(geo, mat, chunkSize = 64) {
        this.chunks = new Map(); // "x,y,z" → InstancedMesh
        this.chunkSize = chunkSize;
    }
    add(pos) {
        const key = this.chunkKey(pos);
        if (!this.chunks.has(key)) {
            this.chunks.set(key, new THREE.InstancedMesh(geo, mat, 1024));
        }
        // ...
    }
    cullChunks(frustum) {
        for (const [key, mesh] of this.chunks) {
            mesh.visible = frustum.intersectsBox(this.chunkBox(key));
        }
    }
}
```

### BatchedMesh (Three.js r170+)
```javascript
const batched = new THREE.BatchedMesh(maxGeoms, maxVerts, maxIdx);
const geoIdA = batched.addGeometry(geoA);
const geoIdB = batched.addGeometry(geoB);
const instA = batched.addInstance(geoIdA);
batched.setMatrixAt(instA, matrixA);
// 매 different geometries 매 single draw call
```

### GPU compute culling (WebGPU)
```javascript
// compute shader: input matrices + frustum planes → atomic counter + visible matrix buffer
const cullPipeline = device.createComputePipeline({...});
pass.setPipeline(cullPipeline);
pass.dispatchWorkgroups(Math.ceil(N / 64));
// then drawIndexedIndirect from visible buffer
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| < 1k static instances | Plain InstancedMesh |
| 1k–100k mostly static | InstancedMesh + manual frustum culling |
| 100k+ static | Chunked InstancedMesh (spatial) + LOD |
| Dynamic per-frame (particles) | Points / GPU particle system |
| Different geometries | BatchedMesh (r170+) |
| Massive (1M+) | WebGPU compute culling + indirect draw |
| Picking 필요 | InstancedMesh + raycaster.firstHitOnly = true |

**기본값**: < 10k는 plain InstancedMesh, 그 이상 매 chunked + LOD.

## 🔗 Graph
- 부모: [[GPU Instancing]] · [[Three.js]]
- 변형: [[BatchedMesh]]
- Adjacent: [[Frustum Culling]] · [[LOD]] · [[Indirect Draw]] · [[WebGPU]]

## 🤖 LLM 활용
**언제**: 매 같은 geometry+material의 N copies, 매 N > 50, 매 draw call 매 hot path bottleneck.
**언제 X**: 매 highly varying geometry (use BatchedMesh), 매 < 50 copies (overhead > benefit), 매 fully dynamic mesh (skinning per instance은 expensive).

## ❌ 안티패턴
- **No bounding 갱신**: 매 instance 매 spread out 매 single boundingSphere가 too large → frustum culling 작동 안 함.
- **매 frame full matrix rebuild**: 매 instance 매 100% update assumption 매 wrong → updateRange 활용.
- **Different materials → multiple InstancedMesh**: 매 점 defeats the purpose. Use texture atlas + instance attribute.
- **Skip LOD**: 매 far instance 매 close instance와 same vertex count → fillrate explosion.
- **InstancedMesh on top of skinned mesh**: 매 shader 매 manual instancing 필요 — Three.js native skinning 매 instance와 conflict.

## 🧪 검증 / 중복
- Verified (Three.js docs r170+, mrdoob InstancedMesh PR, WebGPU spec).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — InstancedMesh 한계 / 해결 패턴 / BatchedMesh + WebGPU compute culling |
