--- id: wiki-2026-0508-instancedmesh-동적-버퍼-확장 title: InstancedMesh 동적 버퍼 확장 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Dynamic InstancedMesh, Resize InstancedMesh, InstancedMesh Growth] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [threejs, webgl, webgpu, performance, buffer] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: Three.js r172 --- # InstancedMesh 동적 버퍼 확장 ## 매 한 줄 > **"매 InstancedMesh 의 capacity 부족 시 GPU buffer 를 reallocate"**. Three.js InstancedMesh 의 `count` 는 fixed allocation. 매 instance 추가가 capacity 초과하면 새 buffer 를 만들고 기존 data 를 copy. 매 amortized O(1) 을 위한 geometric growth 가 정석. ## 매 핵심 ### 매 기본 한계 - 매 `new InstancedMesh(geo, mat, count)` 는 매 `count` 만큼 GPU buffer alloc. - 매 `mesh.count` 변경은 draw 만 줄이지 buffer 는 그대로. - 매 capacity 초과 → 매 새 mesh / 새 InstancedBufferAttribute 가 필요. ### 매 Strategy - **Geometric growth**: 매 capacity 부족 시 ×1.5 또는 ×2 로 resize. - **Pool & free list**: 매 deletion 후 빈 slot 재사용 (compact 회피). - **Chunked**: 매 fixed-size chunk N 개로 운영 (한 chunk 가득 차면 새 chunk). ### 매 응용 1. **Realtime particle spawn / despawn**. 2. **User-placed object editor** (매 추가 무한). 3. **Streaming voxel chunk** (매 LOD 별 instance 수 가변). 4. **NPC spawn system**. ## 💻 패턴 ### 1. Geometric resize utility ```typescript class DynamicInstancedMesh { private mesh: THREE.InstancedMesh; private capacity: number; private size = 0; private freeList: number[] = []; constructor( private geo: THREE.BufferGeometry, private mat: THREE.Material, initialCapacity = 64, private maxCapacity = 1_000_000, ) { this.capacity = initialCapacity; this.mesh = this.makeMesh(initialCapacity); } private makeMesh(cap: number): THREE.InstancedMesh { const m = new THREE.InstancedMesh(this.geo, this.mat, cap); m.instanceMatrix.setUsage(THREE.DynamicDrawUsage); m.count = this.size; return m; } add(matrix: THREE.Matrix4): number { let idx: number; if (this.freeList.length > 0) { idx = this.freeList.pop()!; } else { if (this.size >= this.capacity) this.grow(); idx = this.size++; } this.mesh.setMatrixAt(idx, matrix); this.mesh.instanceMatrix.needsUpdate = true; this.mesh.count = this.size; return idx; } remove(idx: number) { // 매 zero-scale matrix 로 hide → free list 등록 const zero = new THREE.Matrix4().scale(new THREE.Vector3(0, 0, 0)); this.mesh.setMatrixAt(idx, zero); this.mesh.instanceMatrix.needsUpdate = true; this.freeList.push(idx); } private grow() { const newCap = Math.min(this.capacity * 2, this.maxCapacity); if (newCap <= this.capacity) throw new Error('Max capacity hit'); const newMesh = this.makeMesh(newCap); // 매 기존 matrix 복사 const m = new THREE.Matrix4(); for (let i = 0; i < this.size; i++) { this.mesh.getMatrixAt(i, m); newMesh.setMatrixAt(i, m); } // 매 color 도 있으면 copy if (this.mesh.instanceColor) { const oldColor = this.mesh.instanceColor.array as Float32Array; const newColor = new Float32Array(newCap * 3); newColor.set(oldColor); newMesh.instanceColor = new THREE.InstancedBufferAttribute(newColor, 3); } // 매 swap const parent = this.mesh.parent; if (parent) { parent.remove(this.mesh); parent.add(newMesh); } this.mesh.dispose(); // 매 매 critical: GPU buffer free this.mesh = newMesh; this.capacity = newCap; console.log(`[DynamicInstancedMesh] grew → ${newCap}`); } get object(): THREE.InstancedMesh { return this.mesh; } get instanceCount(): number { return this.size - this.freeList.length; } } ``` ### 2. Chunked strategy (매 large N 친화적) ```typescript class ChunkedInstancedMesh { private chunks: THREE.InstancedMesh[] = []; private group = new THREE.Group(); private CHUNK_SIZE = 4096; private currentChunkSize = 0; constructor(private geo: THREE.BufferGeometry, private mat: THREE.Material) { this.addChunk(); } private addChunk() { const m = new THREE.InstancedMesh(this.geo, this.mat, this.CHUNK_SIZE); m.instanceMatrix.setUsage(THREE.DynamicDrawUsage); m.count = 0; this.chunks.push(m); this.group.add(m); this.currentChunkSize = 0; } add(matrix: THREE.Matrix4): { chunk: number; idx: number } { if (this.currentChunkSize >= this.CHUNK_SIZE) this.addChunk(); const chunkIdx = this.chunks.length - 1; const chunk = this.chunks[chunkIdx]; const idx = this.currentChunkSize++; chunk.setMatrixAt(idx, matrix); chunk.count = idx + 1; chunk.instanceMatrix.needsUpdate = true; return { chunk: chunkIdx, idx }; } get object(): THREE.Group { return this.group; } } ``` ### 3. Partial buffer update (updateRange) ```typescript // 매 매 frame 일부만 변경 시 매 전체 upload 회피 function updateInstanceRange( mesh: THREE.InstancedMesh, startIdx: number, count: number ) { mesh.instanceMatrix.updateRange.offset = startIdx * 16; // mat4 = 16 floats mesh.instanceMatrix.updateRange.count = count * 16; mesh.instanceMatrix.needsUpdate = true; } ``` ### 4. WebGPU StorageBuffer 동적 grow ```typescript import { storage, instanceIndex } from 'three/tsl'; class GPUDynamicInstances { private buffer: THREE.StorageBufferAttribute; private capacity: number; constructor(initial = 1024) { this.capacity = initial; this.buffer = new THREE.StorageBufferAttribute(initial, 16); } grow(needed: number) { const newCap = Math.max(this.capacity * 2, needed); const newBuf = new THREE.StorageBufferAttribute(newCap, 16); // 매 GPU-side copy: compute pass 로 old → new // 매 또는 CPU readback 후 재upload (매 비싸지만 단순) newBuf.array.set(this.buffer.array); this.buffer = newBuf; this.capacity = newCap; } } ``` ### 5. Defragment (free list 가 너무 커지면) ```typescript defragment(dim: DynamicInstancedMesh) { // 매 freeList sort 후 살아있는 instance 를 앞으로 compact // 매 user-facing index 가 바뀌므로 index map 도 업데이트해야 함 // ... (구현은 use case 별 — id↔index 매핑 유지가 매 critical) } ``` ### 6. dispose 와 GPU memory 관리 ```typescript function disposeInstanced(mesh: THREE.InstancedMesh) { mesh.geometry.dispose(); if (Array.isArray(mesh.material)) mesh.material.forEach(m => m.dispose()); else mesh.material.dispose(); mesh.dispose(); // 매 InstancedMesh 자체 buffer } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 매 instance 수가 천천히 변동 | Geometric grow ×2 | | 매 instance 수 매 frame 격변 | Pre-alloc max + count 조절 | | 매 매우 큰 N + streaming | Chunked (CHUNK_SIZE 4-16k) | | 매 frequent delete | Free list + zero-scale hide | | 매 free list ≥ 30% | Defragment 한 번 | | 매 GPU-driven spawn | WebGPU StorageBuffer + compute | **기본값**: 매 generic dynamic case → DynamicInstancedMesh (×2 grow + free list + zero-scale remove). ## 🔗 Graph - 부모: [[InstancedMesh 최적화]] · [[Three.js]] - 변형: [[BatchedMesh]] ## 🤖 LLM 활용 **언제**: dynamic spawn/despawn 시스템 설계, free list vs chunked 의 trade-off 설명. **언제 X**: 매 specific WebGL driver memory bug. ## ❌ 안티패턴 - **매 add 마다 new InstancedMesh**: 매 GPU alloc storm — 반드시 amortize. - **dispose() 빠뜨림**: 매 grow 후 옛 mesh GPU buffer 의 leak. - **Linear grow (+1, +1)**: O(N²) total copy. 매 geometric (×1.5 or ×2) 만 사용. - **remove 후 splice**: 매 모든 후속 idx shift → 비싸다. 매 zero-scale + free list. - **매 free list 만 쓰고 defrag 없음**: 매 hide 된 instance 도 vertex shader 실행 (zero-scale 은 깎임). ## 🧪 검증 / 중복 - Verified (Three.js r172 source `src/objects/InstancedMesh.js`). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — dynamic grow + free list + chunked |