Files
2nd/10_Wiki/Topics/Programming & Language/InstancedMesh 동적 버퍼 확장.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
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>
2026-05-20 23:52:15 +09:00

257 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 |