Files
2nd/10_Wiki/Topic_Programming/Programming & Language/InstancedMesh 동적 버퍼 확장.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +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 |