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

8.6 KiB
Raw Blame History

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
Dynamic InstancedMesh
Resize InstancedMesh
InstancedMesh Growth
none A 0.9 applied
threejs
webgl
webgpu
performance
buffer
2026-05-10 pending
language framework
TypeScript 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

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