[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
---
|
||||
id: game-asset-pipeline
|
||||
title: Game Asset Pipeline — Texture / Audio / Mesh 압축
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [game, asset, pipeline, vibe-coding]
|
||||
tech_stack: { language: "Various", applicable_to: ["Game"] }
|
||||
applied_in: []
|
||||
aliases: [asset pipeline, KTX2, basis universal, draco, audio compression, atlas]
|
||||
---
|
||||
|
||||
# Game Asset Pipeline
|
||||
|
||||
> Asset = bundle 의 90%. **Texture (KTX2 / Basis), Mesh (Draco / Meshopt), Audio (Opus), Sprite atlas, model LOD**. 빌드 시 자동 처리 + CDN.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Texture: 가장 큰 부분.
|
||||
- Atlas: 작은 sprite 합치기 — draw call ↓.
|
||||
- LOD: 거리별 mesh 다른 detail.
|
||||
- Streaming: 큰 game = 점진 download.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Texture compression
|
||||
```
|
||||
원본 PNG: 1MB
|
||||
JPEG: 200KB (lossy, no transparency)
|
||||
WebP: 100KB (lossy, transparent OK)
|
||||
AVIF: 80KB (latest)
|
||||
|
||||
GPU 압축 (game-specific):
|
||||
KTX2 + Basis Universal: ~10% size, GPU 직접 사용
|
||||
DDS / S3TC / BC: legacy, vendor-specific
|
||||
ETC2: mobile
|
||||
ASTC: modern mobile/desktop
|
||||
```
|
||||
|
||||
### Basis Universal (cross-GPU)
|
||||
```bash
|
||||
# Convert
|
||||
basisu input.png -ktx2 -comp_level 4 -uastc
|
||||
|
||||
# Output: input.ktx2
|
||||
# Three.js / Bevy 등 직접 load
|
||||
```
|
||||
|
||||
```ts
|
||||
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
||||
|
||||
const ktx2Loader = new KTX2Loader()
|
||||
.setTranscoderPath('/basis/')
|
||||
.detectSupport(renderer);
|
||||
|
||||
ktx2Loader.load('texture.ktx2', (texture) => {
|
||||
// 자동 GPU format 으로 transcoding
|
||||
});
|
||||
```
|
||||
|
||||
→ Web 에서 작은 download + GPU memory 작게.
|
||||
|
||||
### Sprite atlas
|
||||
```bash
|
||||
# TexturePacker / free-tex-packer
|
||||
# 입력: 100 작은 png
|
||||
# 출력: 1 큰 png + JSON
|
||||
```
|
||||
|
||||
```ts
|
||||
// PixiJS spritesheet
|
||||
const sheet = await Assets.load('atlas.json');
|
||||
const sprite = new Sprite(sheet.textures['player_idle.png']);
|
||||
```
|
||||
|
||||
→ 100 draw call → 1.
|
||||
|
||||
### 9-slice / NinePatch (UI)
|
||||
```ts
|
||||
// 큰 button 도 작은 image 로
|
||||
{ left: 10, top: 10, right: 10, bottom: 10 }
|
||||
// corner 안 stretch, edge / center 만.
|
||||
```
|
||||
|
||||
### Mesh compression — Draco
|
||||
```bash
|
||||
# GLTF + Draco
|
||||
gltf-pipeline -i model.glb -o compressed.glb -d
|
||||
```
|
||||
|
||||
```ts
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
dracoLoader.setDecoderPath('/draco/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
loader.load('model.glb', (gltf) => { ... });
|
||||
```
|
||||
|
||||
→ Mesh 90% 감소 가능.
|
||||
|
||||
### Meshopt (alternative, 빠른 decode)
|
||||
```bash
|
||||
gltfpack -i input.glb -o output.glb -cc # compression
|
||||
```
|
||||
|
||||
```ts
|
||||
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
||||
loader.setMeshoptDecoder(MeshoptDecoder);
|
||||
```
|
||||
|
||||
### LOD (Level of Detail)
|
||||
```ts
|
||||
import { LOD } from 'three';
|
||||
|
||||
const lod = new LOD();
|
||||
lod.addLevel(highMesh, 0); // 0-10 m
|
||||
lod.addLevel(midMesh, 10); // 10-50 m
|
||||
lod.addLevel(lowMesh, 50); // 50+ m
|
||||
scene.add(lod);
|
||||
```
|
||||
|
||||
→ 거리별 detail.
|
||||
|
||||
### Audio compression
|
||||
```
|
||||
WAV: 10MB
|
||||
MP3: 1MB (legacy)
|
||||
Ogg Vorbis: 1MB
|
||||
Opus: 0.7MB (modern best)
|
||||
AAC: iOS 친화
|
||||
|
||||
음악 = streaming + lossy
|
||||
효과음 (짧음, 즉시) = WAV / 작은 OGG
|
||||
```
|
||||
|
||||
```bash
|
||||
ffmpeg -i music.wav -c:a libopus -b:a 96k music.opus
|
||||
```
|
||||
|
||||
### Streaming audio
|
||||
```ts
|
||||
// Howler — preload + lazy
|
||||
const music = new Howl({ src: ['music.opus'], preload: true, html5: true });
|
||||
music.play();
|
||||
```
|
||||
|
||||
→ 큰 file 도 stream.
|
||||
|
||||
### Asset bundling (Webpack/Vite)
|
||||
```ts
|
||||
// Static import
|
||||
import playerImg from './assets/player.png';
|
||||
|
||||
// Dynamic
|
||||
const url = new URL('./assets/player.png', import.meta.url).href;
|
||||
|
||||
// Vite — assets folder
|
||||
// Vite 가 hash + optimize.
|
||||
```
|
||||
|
||||
### Bundle / chunks
|
||||
```ts
|
||||
// 큰 model = lazy
|
||||
const heavyModel = lazy(() => import('./HeavyModel'));
|
||||
|
||||
// 또는 preload
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'prefetch';
|
||||
link.href = '/textures/level2.ktx2';
|
||||
document.head.appendChild(link);
|
||||
```
|
||||
|
||||
### CDN serving
|
||||
```
|
||||
모든 asset = CDN.
|
||||
Cache-Control: max-age=31536000, immutable (hash 가 url 안)
|
||||
ETag / Last-Modified 자동.
|
||||
```
|
||||
|
||||
```ts
|
||||
// Hash filename (Vite default)
|
||||
// player.abc123.png — content-addressed
|
||||
```
|
||||
|
||||
### Texture atlas + GPU instancing
|
||||
```ts
|
||||
// 같은 mesh 천 개 = 한 draw call
|
||||
const inst = new InstancedMesh(geometry, material, 1000);
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
matrix.setPosition(x, y, z);
|
||||
inst.setMatrixAt(i, matrix);
|
||||
}
|
||||
inst.instanceMatrix.needsUpdate = true;
|
||||
```
|
||||
|
||||
### Asset budget
|
||||
```
|
||||
Web game (low-end mobile):
|
||||
- Total: ~20MB initial download
|
||||
- JS: ~3MB
|
||||
- Textures: ~5MB
|
||||
- Models: ~3MB
|
||||
- Audio: ~5MB
|
||||
|
||||
Game level:
|
||||
- 1-3 MB compressed level
|
||||
```
|
||||
|
||||
### Hot-reload (dev)
|
||||
```ts
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('./player.png', (newUrl) => {
|
||||
playerSprite.texture = await loader.load(newUrl);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
→ Asset 변경 즉시 반영.
|
||||
|
||||
### Preload screen
|
||||
```tsx
|
||||
<LoadScreen progress={progress}>
|
||||
<Suspense fallback={null}>
|
||||
<Game />
|
||||
</Suspense>
|
||||
</LoadScreen>
|
||||
```
|
||||
|
||||
```ts
|
||||
const assets = [
|
||||
'/player.ktx2', '/level.glb', '/music.opus', /* ... */
|
||||
];
|
||||
let loaded = 0;
|
||||
for (const a of assets) {
|
||||
await preload(a);
|
||||
loaded++;
|
||||
setProgress(loaded / assets.length);
|
||||
}
|
||||
```
|
||||
|
||||
### Tilemap (2D level)
|
||||
```bash
|
||||
# Tiled editor → tmx file
|
||||
# Phaser / Pixi 가 직접 load
|
||||
```
|
||||
|
||||
### Animation export
|
||||
```
|
||||
- 2D sprite: Aseprite / TexturePacker → spritesheet
|
||||
- 3D: Blender → GLB (animations clip 포함)
|
||||
- Spine / DragonBones: skeletal 2D animation
|
||||
```
|
||||
|
||||
### Texture mipmaps
|
||||
```
|
||||
GPU 가 거리별 다른 size 자동.
|
||||
KTX2 안 자동 포함.
|
||||
|
||||
Manual:
|
||||
texture.generateMipmaps = true;
|
||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
```
|
||||
|
||||
### Asset versioning + cache invalidation
|
||||
```
|
||||
manifest.json:
|
||||
{
|
||||
"player.png": "abc123",
|
||||
"level1.glb": "def456"
|
||||
}
|
||||
|
||||
→ 변경 시 hash 변경 → CDN 자동.
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| Asset | 추천 |
|
||||
|---|---|
|
||||
| Web game texture | KTX2 + Basis |
|
||||
| Mobile native | ASTC / ETC2 |
|
||||
| Sprite | Atlas + WebP |
|
||||
| 3D model | GLB + Draco / Meshopt |
|
||||
| Music | Opus streaming |
|
||||
| SFX | Ogg / WAV |
|
||||
| UI icon | SVG / WebP |
|
||||
| Big level | Streamed chunks |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **PNG / JPG GPU 사용**: GPU 가 RGBA 압축 X. KTX2.
|
||||
- **개별 sprite**: draw call 폭발. atlas.
|
||||
- **Mesh 압축 X**: GLB 100MB.
|
||||
- **모든 quality 제공**: bundle 폭발. LOD + dynamic.
|
||||
- **Audio WAV**: 큰 file. Opus.
|
||||
- **Asset hot path 매번 load**: cache.
|
||||
- **Preload 모든 거**: 첫 load 매우 길음. lazy.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- KTX2 + Draco + Opus + atlas 4종 = 90% 감소.
|
||||
- LOD + mipmaps + instancing = 60fps.
|
||||
- CDN + immutable cache.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Frontend_Image_Optimization]]
|
||||
- [[Mobile_App_Size_Optimization]]
|
||||
- [[Frontend_Three_R3F]]
|
||||
Reference in New Issue
Block a user