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

7.9 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
InstancedMesh
GPU Instancing
Three.js Instancing
none A 0.9 applied
threejs
webgl
webgpu
rendering
performance
2026-05-10 pending
language framework
TypeScript Three.js r172 / WebGPU

InstancedMesh 최적화

매 한 줄

"매 동일 geometry 의 N 개를 1 draw call 로". InstancedMesh 는 매 같은 mesh 를 instance attribute (matrix, color) 만 바꿔 GPU 에 한 번에 제출. 매 forest, particle, crowd 같은 thousands-of-objects scene 에서 50-1000x throughput.

매 핵심

매 작동 원리

  • 매 vertex shader 는 gl_InstanceID (WebGL2) / instance_index (WebGPU) 로 per-instance 데이터 lookup.
  • 매 instanceMatrix (mat4) 는 default attribute. 매 추가로 instanceColor, custom attribute 가능.
  • 매 draw call: gl.drawElementsInstanced(mode, count, type, offset, instanceCount).

매 비용 분석

  • CPU 절감: 매 N draw call → 1. 매 binding state 변경 N → 1.
  • GPU 비용: 매 동일 (vertex 처리 N×). 매 절감은 driver overhead 에서.
  • Break-even: 매 보통 ~50-100 instance 부터 이득. 매 이하면 batched geometry 가 더 빠를 수도.

매 응용

  1. Forest / vegetation (10k trees).
  2. Particle system (smoke, sparks).
  3. Crowd / NPC swarm.
  4. Voxel chunk rendering.
  5. UI marker overlay (map pins).

💻 패턴

1. 기본 InstancedMesh 셋업 (Three.js r172)

import * as THREE from 'three';

const geo = new THREE.BoxGeometry(1, 1, 1);
const mat = new THREE.MeshStandardMaterial({ color: 0x88aaff });
const COUNT = 10_000;
const mesh = new THREE.InstancedMesh(geo, mat, COUNT);
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);  // 매 자주 업데이트면

const m = new THREE.Matrix4();
const q = new THREE.Quaternion();
const s = new THREE.Vector3(1, 1, 1);
const p = new THREE.Vector3();

for (let i = 0; i < COUNT; i++) {
    p.set((Math.random() - 0.5) * 200, 0, (Math.random() - 0.5) * 200);
    q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.random() * Math.PI * 2);
    m.compose(p, q, s);
    mesh.setMatrixAt(i, m);
}
mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);

2. Per-instance color (vertex color attribute)

const colors = new Float32Array(COUNT * 3);
for (let i = 0; i < COUNT; i++) {
    colors[i*3] = Math.random();
    colors[i*3+1] = Math.random();
    colors[i*3+2] = Math.random();
}
mesh.instanceColor = new THREE.InstancedBufferAttribute(colors, 3);

3. Frustum culling per-instance (BVH-based)

import { computeBoundsTree, MeshBVH } from 'three-mesh-bvh';

// 매 default InstancedMesh 는 whole-mesh frustum culling 만 함.
// 매 instance-level 은 manual: BVH 로 visible instance 만 update.
const bvh = new MeshBVH(geo);
const visibleMatrices: THREE.Matrix4[] = [];
const frustum = new THREE.Frustum().setFromProjectionMatrix(
    new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
);

let visibleCount = 0;
for (let i = 0; i < COUNT; i++) {
    mesh.getMatrixAt(i, m);
    p.setFromMatrixPosition(m);
    if (frustum.containsPoint(p)) {
        mesh.setMatrixAt(visibleCount++, m);
    }
}
mesh.count = visibleCount;  // 매 핵심: count 만 줄여 draw 절감
mesh.instanceMatrix.needsUpdate = true;

4. WebGPU instancing (Three.js r172 WebGPURenderer)

import { WebGPURenderer, MeshBasicNodeMaterial } from 'three/webgpu';
import { instanceIndex, vec3, color } from 'three/tsl';

const mat = new MeshBasicNodeMaterial();
// 매 TSL 로 per-instance color 계산
mat.colorNode = color(vec3(
    instanceIndex.toFloat().div(COUNT).fract(),
    0.5,
    0.8
));
const mesh = new THREE.InstancedMesh(geo, mat, COUNT);

5. LOD + InstancedMesh 결합

class InstancedLOD {
    constructor(private high: THREE.InstancedMesh,
                private mid: THREE.InstancedMesh,
                private low: THREE.InstancedMesh) {}

    update(camera: THREE.Camera, positions: THREE.Vector3[]) {
        let h = 0, m = 0, l = 0;
        const matrix = new THREE.Matrix4();
        for (const p of positions) {
            const d = p.distanceTo(camera.position);
            const target = d < 30 ? this.high : d < 100 ? this.mid : this.low;
            const idx = d < 30 ? h++ : d < 100 ? m++ : l++;
            matrix.setPosition(p);
            target.setMatrixAt(idx, matrix);
        }
        this.high.count = h; this.mid.count = m; this.low.count = l;
        [this.high, this.mid, this.low].forEach(x => x.instanceMatrix.needsUpdate = true);
    }
}

6. GPU compute 로 instance 위치 (WebGPU)

import { Fn, instanceIndex, storage, vec3, time } from 'three/tsl';
import { compute } from 'three/tsl';

const positionBuffer = storage(new THREE.StorageBufferAttribute(COUNT, 3), 'vec3');

const updateFn = Fn(() => {
    const i = instanceIndex.toFloat();
    const t = time;
    positionBuffer.element(instanceIndex).assign(
        vec3(i.mul(0.1).sin().mul(50), t.add(i).sin().mul(5), i.mul(0.1).cos().mul(50))
    );
});

const computePass = updateFn().compute(COUNT);
renderer.computeAsync(computePass);  // 매 frame 마다

7. Memory layout 최적화 (matrix → quat+pos+scale)

// 매 mat4 (16 floats × 4B = 64B) 대신 quat (4) + pos (3) + scale (1) = 32B
const compactBuffer = new Float32Array(COUNT * 8);
mesh.geometry.setAttribute('iQuat',
    new THREE.InstancedBufferAttribute(compactBuffer, 4, false, 1).setUsage(THREE.DynamicDrawUsage));
// shader 에서 quat → matrix reconstruct

8. Sort by depth (transparency)

const indices = Array.from({length: COUNT}, (_, i) => i);
indices.sort((a, b) => {
    mesh.getMatrixAt(a, m); const da = p.setFromMatrixPosition(m).distanceTo(camera.position);
    mesh.getMatrixAt(b, m); const db = p.setFromMatrixPosition(m).distanceTo(camera.position);
    return db - da;  // 매 back-to-front
});
const sorted = new Float32Array(COUNT * 16);
for (let i = 0; i < COUNT; i++) {
    mesh.getMatrixAt(indices[i], m);
    m.toArray(sorted, i * 16);
}
mesh.instanceMatrix.array.set(sorted);
mesh.instanceMatrix.needsUpdate = true;

매 결정 기준

상황 Approach
Static, identical mesh × thousands InstancedMesh
Static, varied meshes BatchedMesh (Three.js r158+)
매 < 50 instance 매 그냥 separate Mesh, 차이 없음
매 frequent matrix update setUsage(DynamicDrawUsage) + partial updateRange
Per-instance custom data InstancedBufferAttribute
GPU-driven motion TSL compute + storage buffer

기본값: 매 ≥100 identical instance → InstancedMesh + DynamicDrawUsage + frustum culling.

🔗 Graph

  • 부모: Three.js · GPU Instancing · WebGL/WebGPU
  • 변형: BatchedMesh
  • Adjacent: InstancedMesh 동적 버퍼 확장 · Frustum Culling · LOD

🤖 LLM 활용

언제: instance 수에 따른 기법 선택, draw call 분석 해석, TSL compute shader 작성. 언제 X: 매 specific GPU/driver bug 진단 (실측 profile 없이).

안티패턴

  • 매 frame 마다 setMatrixAt 전체 N: 매 변하지 않는 instance 까지 update → CPU bound. 매 dirty flag 사용.
  • DynamicDrawUsage 빠뜨림: 매 default StaticDrawUsage 면 매 update 시 driver hint mismatch.
  • count 줄이지 않고 culling: 매 invisible 도 vertex shader 실행됨.
  • 너무 작은 N (< 50): 매 instancing overhead 가 이득보다 큼.

🧪 검증 / 중복

  • Verified (Three.js r172 docs, WebGPU spec, Khronos WebGL2 spec).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — InstancedMesh 패턴 + WebGPU TSL compute