f8b21af4be
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>
8.6 KiB
8.6 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-instancedmesh-동적-버퍼-확장 | InstancedMesh 동적 버퍼 확장 | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
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).
매 응용
- Realtime particle spawn / despawn.
- User-placed object editor (매 추가 무한).
- Streaming voxel chunk (매 LOD 별 instance 수 가변).
- NPC spawn system.
💻 패턴
1. Geometric resize utility
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 친화적)
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)
// 매 매 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
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 가 너무 커지면)
defragment(dim: DynamicInstancedMesh) {
// 매 freeList sort 후 살아있는 instance 를 앞으로 compact
// 매 user-facing index 가 바뀌므로 index map 도 업데이트해야 함
// ... (구현은 use case 별 — id↔index 매핑 유지가 매 critical)
}
6. dispose 와 GPU memory 관리
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 |