---
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 |
