f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
172 lines
6.7 KiB
Markdown
172 lines
6.7 KiB
Markdown
---
|
||
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 |
|