Files
2nd/10_Wiki/Topics/AI_and_ML/20k skinned instances demo.md
T
2026-05-10 22:08:15 +09:00

7.5 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-20k-skinned-instances-demo 20k Skinned Instances Demo (Three.js) 10_Wiki/Topics verified self
InstancedMesh2 demo
large skinned mesh
three.js performance demo
GPU skinning
none B 0.85 applied
three-js
webgl
performance
skinned-mesh
gpu-skinning
lod
frustum-culling
instancing
2026-05-09 pending
language framework
TypeScript Three.js / WebGL

20k Skinned Instances Demo

📌 한 줄 통찰

20,000 character 의 same scene + 5 draw call only. agargaro 의 InstancedMesh2 + frustum culling + LOD + animation throttle. Mobile 도 3000+ instance 60fps.

📖 핵심

매 demo 의 capability

  • Desktop: 20,000 skinned instance @ 60 FPS.
  • Mobile: 3,000 instance @ 60 FPS.
  • Draw calls: 5 only (despite 20k unit).
  • GPU: integrated 도 OK.

→ 매 traditional 의 100-1000 instance 의 limit 의 break.

매 optimization technique

1. Frustum culling

  • 매 camera 의 outside instance 의 skip.
  • 매 bone update 의 only visible.

2. Distance-based animation

  • Near: 60 FPS bone update.
  • Mid: 30 FPS.
  • Far: 10 FPS.
  • Very far: 0 (static pose).

→ 매 update cost 의 80% saving.

3. Multi-LOD

  • LOD 0: 매 detailed mesh + bone.
  • LOD 1: 매 simplified mesh.
  • LOD 2: 매 imposter (billboard).

→ 매 distance 의 different polygon count.

4. GPU skinning

  • 매 bone matrix 의 texture 저장.
  • 매 vertex shader 의 calculate.
  • CPU 의 free.

5. Single material / atlas

  • 매 instance 의 same material.
  • 매 atlas texture (1 texture, multiple variation).

6. Indirect rendering

  • GPU 의 매 instance 의 visibility 결정.
  • CPU → GPU transfer 최소화.

매 architecture

Scene
└── InstancedMesh2 (1)
    ├── Geometry: skinned mesh (1)
    ├── Material: shared (1)
    ├── BoneTexture: 매 instance 의 bone matrix (RGBA float)
    └── Per-instance:
        ├── Position
        ├── Rotation
        ├── Scale
        ├── Color (optional)
        └── Animation state (frame, speed)

→ 1 InstancedMesh2 = 1 draw call.

매 use case

Game (RTS / open world)

  • 매 1000+ unit (StarCraft 식).
  • 매 crowd (city, stadium).

Visualization

  • 매 large dataset (data point 의 character).
  • 매 scientific (molecule, particle).

Simulation

  • 매 swarm.
  • 매 evacuation.
  • 매 pedestrian.

Web 3D

  • 매 metaverse-style.
  • 매 large social space.

💻 Code

Setup

import { InstancedMesh2 } from '@three.ez/instanced-mesh';
import * as THREE from 'three';

// Load skinned mesh
const loader = new GLTFLoader();
const gltf = await loader.loadAsync('character.glb');
const skinnedMesh = gltf.scene.children[0] as THREE.SkinnedMesh;

// Create InstancedMesh2
const instancedMesh = new InstancedMesh2(
  skinnedMesh.geometry,
  skinnedMesh.material,
  {
    capacity: 20000,
    createEntities: true,
    skinned: true,   // GPU skinning
  }
);

// Add instances
for (let i = 0; i < 20000; i++) {
  instancedMesh.addInstances(1, (instance) => {
    instance.position.set(
      (Math.random() - 0.5) * 1000,
      0,
      (Math.random() - 0.5) * 1000
    );
    instance.rotation.y = Math.random() * Math.PI * 2;
  });
}

scene.add(instancedMesh);

Frustum culling + distance animation

function animate() {
  const cameraPos = camera.position;
  
  instancedMesh.instances.forEach((instance, i) => {
    const dist = instance.position.distanceTo(cameraPos);
    
    // Distance-based update rate
    if (dist < 50) {
      instance.updateAnimation(deltaTime);   // 60 FPS
    } else if (dist < 200) {
      if (frame % 2 === 0) instance.updateAnimation(deltaTime * 2);   // 30 FPS
    } else if (dist < 500) {
      if (frame % 6 === 0) instance.updateAnimation(deltaTime * 6);   // 10 FPS
    }
    // > 500: no animation update (static pose)
  });
  
  // Auto frustum culling
  instancedMesh.performFrustumCulling(camera);
  
  renderer.render(scene, camera);
}

Multi-LOD

const lod0 = new InstancedMesh2(highPolyGeo, mat, { capacity: 5000, skinned: true });
const lod1 = new InstancedMesh2(midPolyGeo, mat, { capacity: 10000, skinned: true });
const lod2 = new InstancedMesh2(impostorGeo, mat, { capacity: 5000 });

function updateLOD(instances) {
  instances.forEach((inst, i) => {
    const d = inst.distanceToCamera(camera);
    if (d < 50) inst.assignTo(lod0);
    else if (d < 200) inst.assignTo(lod1);
    else inst.assignTo(lod2);
  });
}

GPU skinning (custom shader)

// Vertex shader
attribute vec4 skinIndices;
attribute vec4 skinWeights;

uniform sampler2D boneTexture;   // 매 instance 의 bone matrix
uniform float boneTextureSize;

mat4 getBoneMatrix(float index, float instanceIndex) {
  float u = (index * 4.0 + 0.5) / boneTextureSize;
  float v = (instanceIndex + 0.5) / boneTextureSize;
  return mat4(
    texture2D(boneTexture, vec2(u, v)),
    texture2D(boneTexture, vec2(u + 1.0/boneTextureSize, v)),
    texture2D(boneTexture, vec2(u + 2.0/boneTextureSize, v)),
    texture2D(boneTexture, vec2(u + 3.0/boneTextureSize, v))
  );
}

void main() {
  mat4 boneMat = 
    getBoneMatrix(skinIndices.x, gl_InstanceID) * skinWeights.x +
    getBoneMatrix(skinIndices.y, gl_InstanceID) * skinWeights.y +
    getBoneMatrix(skinIndices.z, gl_InstanceID) * skinWeights.z +
    getBoneMatrix(skinIndices.w, gl_InstanceID) * skinWeights.w;
  
  vec4 transformed = boneMat * vec4(position, 1.0);
  gl_Position = projectionMatrix * modelViewMatrix * transformed;
}

→ 매 vertex 의 GPU 가 calculate.

Performance metric

const stats = new Stats();
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  
  // ... render
  
  stats.end();
  requestAnimationFrame(animate);
}

console.log({
  draws: renderer.info.render.calls,
  triangles: renderer.info.render.triangles,
  geometries: renderer.info.memory.geometries,
});
// Goal: draws < 10, FPS = 60

🤔 결정 기준

Instance count Approach
< 100 Native skinned mesh (each its own)
100-1000 InstancedMesh2 + frustum cull
1000-10000 + LOD + distance animation
10000+ + GPU skinning + impostor
Mobile 3000 max + heavy LOD

기본값: InstancedMesh2 + 5 optimization (frustum, LOD, animation throttle, GPU skin, atlas).

🔗 Graph

🤖 LLM 활용

언제: 매 large character scene 의 design. 매 mobile / web 3D 의 performance. 언제 X: 매 small scene. 매 specific Unity / Unreal (different).

안티패턴

  • Native skinned + 1000 instance: 30 FPS.
  • No LOD + variable distance: GPU waste.
  • Bone update 60 FPS 매 instance: CPU 의 bottleneck.
  • No frustum cull: hidden update.
  • Multiple material per instance: 매 draw call 의 multiply.

🧪 검증 / 중복

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-09 Manual cleanup — 5 optimization + Three.js code + GPU skinning shader