--- id: wiki-2026-0508-instancedmesh-최적화 title: InstancedMesh 최적화 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [InstancedMesh, GPU Instancing, Three.js Instancing] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [threejs, webgl, webgpu, rendering, performance] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: Three.js r172 / WebGPU --- # InstancedMesh 최적화 ## 매 한 줄 > **"매 동일 geometry 의 N 개를 1 draw call 로"**. InstancedMesh 는 매 같은 mesh 를 instance attribute (matrix, color) 만 바꿔 GPU 에 한 번에 제출. 매 forest, particle, crowd 같은 thousands-of-objects scene 에서 50-1000x throughput. ## 매 핵심 ### 매 작동 원리 - 매 vertex shader 는 `gl_InstanceID` (WebGL2) / `instance_index` (WebGPU) 로 per-instance 데이터 lookup. - 매 instanceMatrix (mat4) 는 default attribute. 매 추가로 instanceColor, custom attribute 가능. - 매 draw call: `gl.drawElementsInstanced(mode, count, type, offset, instanceCount)`. ### 매 비용 분석 - **CPU 절감**: 매 N draw call → 1. 매 binding state 변경 N → 1. - **GPU 비용**: 매 동일 (vertex 처리 N×). 매 절감은 driver overhead 에서. - **Break-even**: 매 보통 ~50-100 instance 부터 이득. 매 이하면 batched geometry 가 더 빠를 수도. ### 매 응용 1. **Forest / vegetation** (10k trees). 2. **Particle system** (smoke, sparks). 3. **Crowd / NPC swarm**. 4. **Voxel chunk rendering**. 5. **UI marker overlay** (map pins). ## 💻 패턴 ### 1. 기본 InstancedMesh 셋업 (Three.js r172) ```typescript import * as THREE from 'three'; const geo = new THREE.BoxGeometry(1, 1, 1); const mat = new THREE.MeshStandardMaterial({ color: 0x88aaff }); const COUNT = 10_000; const mesh = new THREE.InstancedMesh(geo, mat, COUNT); mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // 매 자주 업데이트면 const m = new THREE.Matrix4(); const q = new THREE.Quaternion(); const s = new THREE.Vector3(1, 1, 1); const p = new THREE.Vector3(); for (let i = 0; i < COUNT; i++) { p.set((Math.random() - 0.5) * 200, 0, (Math.random() - 0.5) * 200); q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.random() * Math.PI * 2); m.compose(p, q, s); mesh.setMatrixAt(i, m); } mesh.instanceMatrix.needsUpdate = true; scene.add(mesh); ``` ### 2. Per-instance color (vertex color attribute) ```typescript const colors = new Float32Array(COUNT * 3); for (let i = 0; i < COUNT; i++) { colors[i*3] = Math.random(); colors[i*3+1] = Math.random(); colors[i*3+2] = Math.random(); } mesh.instanceColor = new THREE.InstancedBufferAttribute(colors, 3); ``` ### 3. Frustum culling per-instance (BVH-based) ```typescript import { computeBoundsTree, MeshBVH } from 'three-mesh-bvh'; // 매 default InstancedMesh 는 whole-mesh frustum culling 만 함. // 매 instance-level 은 manual: BVH 로 visible instance 만 update. const bvh = new MeshBVH(geo); const visibleMatrices: THREE.Matrix4[] = []; const frustum = new THREE.Frustum().setFromProjectionMatrix( new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse) ); let visibleCount = 0; for (let i = 0; i < COUNT; i++) { mesh.getMatrixAt(i, m); p.setFromMatrixPosition(m); if (frustum.containsPoint(p)) { mesh.setMatrixAt(visibleCount++, m); } } mesh.count = visibleCount; // 매 핵심: count 만 줄여 draw 절감 mesh.instanceMatrix.needsUpdate = true; ``` ### 4. WebGPU instancing (Three.js r172 WebGPURenderer) ```typescript import { WebGPURenderer, MeshBasicNodeMaterial } from 'three/webgpu'; import { instanceIndex, vec3, color } from 'three/tsl'; const mat = new MeshBasicNodeMaterial(); // 매 TSL 로 per-instance color 계산 mat.colorNode = color(vec3( instanceIndex.toFloat().div(COUNT).fract(), 0.5, 0.8 )); const mesh = new THREE.InstancedMesh(geo, mat, COUNT); ``` ### 5. LOD + InstancedMesh 결합 ```typescript class InstancedLOD { constructor(private high: THREE.InstancedMesh, private mid: THREE.InstancedMesh, private low: THREE.InstancedMesh) {} update(camera: THREE.Camera, positions: THREE.Vector3[]) { let h = 0, m = 0, l = 0; const matrix = new THREE.Matrix4(); for (const p of positions) { const d = p.distanceTo(camera.position); const target = d < 30 ? this.high : d < 100 ? this.mid : this.low; const idx = d < 30 ? h++ : d < 100 ? m++ : l++; matrix.setPosition(p); target.setMatrixAt(idx, matrix); } this.high.count = h; this.mid.count = m; this.low.count = l; [this.high, this.mid, this.low].forEach(x => x.instanceMatrix.needsUpdate = true); } } ``` ### 6. GPU compute 로 instance 위치 (WebGPU) ```typescript import { Fn, instanceIndex, storage, vec3, time } from 'three/tsl'; import { compute } from 'three/tsl'; const positionBuffer = storage(new THREE.StorageBufferAttribute(COUNT, 3), 'vec3'); const updateFn = Fn(() => { const i = instanceIndex.toFloat(); const t = time; positionBuffer.element(instanceIndex).assign( vec3(i.mul(0.1).sin().mul(50), t.add(i).sin().mul(5), i.mul(0.1).cos().mul(50)) ); }); const computePass = updateFn().compute(COUNT); renderer.computeAsync(computePass); // 매 frame 마다 ``` ### 7. Memory layout 최적화 (matrix → quat+pos+scale) ```typescript // 매 mat4 (16 floats × 4B = 64B) 대신 quat (4) + pos (3) + scale (1) = 32B const compactBuffer = new Float32Array(COUNT * 8); mesh.geometry.setAttribute('iQuat', new THREE.InstancedBufferAttribute(compactBuffer, 4, false, 1).setUsage(THREE.DynamicDrawUsage)); // shader 에서 quat → matrix reconstruct ``` ### 8. Sort by depth (transparency) ```typescript const indices = Array.from({length: COUNT}, (_, i) => i); indices.sort((a, b) => { mesh.getMatrixAt(a, m); const da = p.setFromMatrixPosition(m).distanceTo(camera.position); mesh.getMatrixAt(b, m); const db = p.setFromMatrixPosition(m).distanceTo(camera.position); return db - da; // 매 back-to-front }); const sorted = new Float32Array(COUNT * 16); for (let i = 0; i < COUNT; i++) { mesh.getMatrixAt(indices[i], m); m.toArray(sorted, i * 16); } mesh.instanceMatrix.array.set(sorted); mesh.instanceMatrix.needsUpdate = true; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Static, identical mesh × thousands | `InstancedMesh` | | Static, varied meshes | `BatchedMesh` (Three.js r158+) | | 매 < 50 instance | 매 그냥 separate Mesh, 차이 없음 | | 매 frequent matrix update | `setUsage(DynamicDrawUsage)` + partial `updateRange` | | Per-instance custom data | `InstancedBufferAttribute` | | GPU-driven motion | TSL compute + storage buffer | **기본값**: 매 ≥100 identical instance → InstancedMesh + DynamicDrawUsage + frustum culling. ## 🔗 Graph - 부모: [[Three.js]] · [[GPU Instancing]] · [[WebGL/WebGPU]] - 변형: [[BatchedMesh]] · [[InstancedBufferGeometry]] - 응용: [[Particle Systems]] · [[Vegetation Rendering]] · [[Voxel Engines]] - Adjacent: [[InstancedMesh 동적 버퍼 확장]] · [[Frustum Culling]] · [[LOD]] ## 🤖 LLM 활용 **언제**: instance 수에 따른 기법 선택, draw call 분석 해석, TSL compute shader 작성. **언제 X**: 매 specific GPU/driver bug 진단 (실측 profile 없이). ## ❌ 안티패턴 - **매 frame 마다 setMatrixAt 전체 N**: 매 변하지 않는 instance 까지 update → CPU bound. 매 dirty flag 사용. - **DynamicDrawUsage 빠뜨림**: 매 default StaticDrawUsage 면 매 update 시 driver hint mismatch. - **count 줄이지 않고 culling**: 매 invisible 도 vertex shader 실행됨. - **너무 작은 N (< 50)**: 매 instancing overhead 가 이득보다 큼. ## 🧪 검증 / 중복 - Verified (Three.js r172 docs, WebGPU spec, Khronos WebGL2 spec). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — InstancedMesh 패턴 + WebGPU TSL compute |