Any3DAny3D
·Any3D Team

Blender에서 출시까지: 엔드투엔드 압축 실전

3d-compressionpipelinetexture-compressionvertex-compressiongltf

시리즈도 여기까지 와서, 도구, 원리, 선정을 모두 다뤘습니다. 마지막 이 글에서 모든 것을 굴러가는 파이프라인으로 묶습니다: 실제 Blender 모델에서 출발해, 한 단계씩 압축하고, 각 단계에서 파일 크기, VRAM, 로드 시간의 변화를 기록하고, 마지막에 50MB의 중량급에서 폰에서 즉시 열리는 5MB 모델이 될 수 있는지 봅니다.

대상 독자: 앞선 다섯 편을 읽고 실제로 손대려는 사람. 새 개념은 안 내놓습니다, 복사 가능한 흐름, 명령, 스크립트만 놓습니다.

출발점: 실제 PBR 모델

매우 전형적인 이커머스 표시 모델을 샘플로 씁니다: 고정밀 제품 모델에 완전한 PBR 맵 포함.

초기 지표수치
Blender 소스 파일~120MB(내보내지 않은 하이폴리 포함)
내보낸 GLB(float32 + PNG)~50MB
정점 수약 18만
6장 4096×4096(albedo, normal, roughness, metallic, AO, emissive)
VRAM 사용량(6장 모두 해제)~520MB
목표파일 ≤ 5MB, VRAM 제어 가능, 모바일에서 즉시 열림

50MB 파일에 520MB VRAM — 이 모델은 모바일에서 확실히 크래시 납니다. 한 단계씩 갑시다.

0단계: Blender에서 올바르게 내보내기

압축의 첫 관문은 사실 내보내기이며, 많은 사람이 여기서 피를 봅니다.

Blender에서 glTF를 내보낼 때 핵심 설정:

  • 포맷: glTF Binary (.glb)(단일 파일, 전송 편리)
  • 지오메트리: Normals, Tangents 체크(PBR 노멀맵에는 접선 필요)
  • UV: 확실히 내보내기(기본 켜짐)
  • 텍스처: Automatic 또는 JPEG(여기서 텍스처 포맷은 상관없음, 나중에 다시 압축하지만, 내보내졌는지는 필수)
  • 압축: Blender 자체 메시 압축은 체크하지 마세요, 더 전문적인 도구를 씁니다
  • 트랜스폼: +Y Up(glTF 표준)
  • 데이터: 필요한 것만 체크(애니메이션, 카메라, 라이트는 필요 없으면 내보내지 않아 크기 절감)

내보낸 후 model.glb: 50MB, PNG 맵 6장, float32 정점. 이게 베이스라인입니다.

첫 번째 흔한 함정이 바로 여기: Blender는 기본적으로 미사용 메시와 숨긴 보조 오브젝트도 함께 내보냅니다. 내보내기 전에 File > Clean Up > Purge Orphans을 실행하고, 아웃라이너에서 내보낼 오브젝트만 선택하세요.

엔드투엔드 파이프라인 전경

전체 라인을 그려 머릿속에 지도를 만듭니다:

Blender 소스 파일
   │  .glb 내보내기(float32 + PNG)            50MB
   ▼
[1] 중복 제거 + 중복 정점 웰드(gltf-transform)   ~45MB
   │
[2] 정점 압축: MeshOpt(gltfpack / gltf-transform) ~30MB
   │
[3] 텍스처 압축: PNG → KTX2(ETC1S/UASTC)     ~6MB
   │
[4](선택) 지오메트리 단순화 LOD(simplify)      ~4-5MB
   ▼
최종 model-final.glb                          ~5MB
   │
엔진 로드(Three.js / Babylon.js) → 런타임 트랜스코드 → 출시

각 단계의 수치는 아래 표에서 실시간 추적합니다.

툴체인: 어느 것을 고를까

압축 도구는 여럿 있습니다, 잘못 고르지 않도록 비교를:

도구강점약점적합
gltf-transform올라운더, 텍스처+정점을 한 방에, API화·스크립트화 가능극한 압축률은 전용 도구에 못 미침메인 추천, 압도적 다수의 씬
gltfpack정점 압축 전문, MeshOpt 네이티브 지원텍스처 압축 약함정점 밀집, MeshOpt 세 제어
toktx텍스처 압축에 가장 전문, 파라미터 최다텍스처만 처리, 모델 전체 불가단일 텍스처 정밀 튜닝
gltf-pipeline원로, Draco 지원유지보수 비활성, 기능 적음기존 Draco 레거시 프로젝트
온라인 도구(gltf.report)제로 설치자동화·대량 처리 부적합시험, 일회성 작업

메인 추천: gltf-transform으로 전체 흐름을 굴리고, 필요 시 gltfpack으로 정점을, toktx로 단일 텍스처를 보충합니다. 아래 모든 단계는 gltf-transform 기반입니다.

1단계: 중복 제거 + 웰드

모델에는 흔히 중복 정점, 미사용 노드와 머티리얼이 있습니다. 먼저 한 번 청소합니다.

gltf-transform optimize model.glb step1.glb --weld --prune
단계파일 크기VRAM변화
베이스라인50MB~520MB
Step 1 중복 제거45MB~520MB-5MB(VRM 불변, 텍스처가 여전히 있어서)

VRAM은 거의 안 움직입니다, 예상대로입니다. 중복 제거가 주로 절약하는 건 정점과 구조이고, 텍스처가 VRAM의 대부분입니다.

2단계: 정점 압축 MeshOpt

gltf-transform optimize step1.glb step2.glb --meshopt --weld --prune

--meshopt는 정점을 16비트로 양자화하고 MeshOpt 무손실 부호화를 적용, 자동으로 EXT_meshopt_compression 확장을 추가합니다.

단계파일 크기VRAM변화
Step 145MB~520MB
Step 2 + MeshOpt30MB~520MB-15MB(정점 부분)

VRAM이 여전히 ~520MB? 맞습니다 — 정점은 VRAM에서 작은 비중(10-20%)이라, 정점을 잘라도 VRAM 영향이 제한적입니다. 진짜 VRAM 괴물은 텍스처, 다음에 해결합니다.

3단계: 텍스처 압축 PNG → KTX2

이 단계는 비용 대비 효과의 왕입니다.

gltf-transform optimize step2.glb step3.glb \
  --texture-compress basisu \
  --meshopt --weld --prune

--texture-compress basisu는 각 맵을 자동 판단합니다: 컬러 맵(albedo, emissive)은 ETC1S, 데이터 맵(normal, roughness, metallic, AO)은 UASTC.

단계파일 크기VRAM변화
Step 230MB~520MB
Step 3 + KTX26MB~70MB-24MB 파일 / -450MB VRAM

이 단계가 파이프라인 전체의 전환점입니다:

  • 파일이 30MB에서 6MB로 하락
  • VRAM이 520MB에서 약 70MB로 하락 — 6장의 4096 맵이 "해제 후 raw pixel"에서 "블록 압축"으로 바뀌어, 각 장이 ~87MB에서 ~11-14MB로

VRAM이 한 자릿수 하락했고, 이게 모바일에서 도는지의 핵심입니다.

4단계:(선택) 지오메트리 단순화

더 작길 원하고 씬이 정점 정밀도 저하를 허용하면, 지오메트리 단순화를 추가합니다.

gltf-transform optimize step3.glb final.glb \
  --texture-compress basisu \
  --meshopt \
  --simplify --simplify-ratio 0.5 \
  --weld --prune

--simplify-ratio 0.5는 약 50% 정점을 남긴다는 뜻입니다.

단계파일 크기VRAM변화
Step 36MB~70MB
Step 4 + 단순화 0.54.5MB~70MB-1.5MB(VRM 거의 불변)

단순화는 주로 파일 크기를 절약하고 VRAM 영향은 작습니다. 대가는 모델 디테일 저하 — 근거리에서 눈에 띕니다. 이커머스 상품 페이지는 보통 과도한 단순화 비추천, 건축/대형 씬은 아주 적합합니다.

효과 추적 총표

네 단계를 겹쳐서 전체상을 봅니다(위 샘플 기반, 수치는 규모의 참고용):

단계파일 크기VRAM누적 감소율
베이스라인(float32 + PNG)50MB~520MB
+ 중복 제거 & 웰드45MB~520MB-10%
+ MeshOpt 정점30MB~520MB-40%
+ KTX2 텍스처6MB~70MB-88% 파일 / -87% VRAM
+ 지오메트리 단순화(0.5)4.5MB~70MB-91% 파일

결론: 텍스처 압축이 용량과 VRAM 수익의 압도적 다수를 담당합니다. 정점 압축은 보너스이고, 텍스처 압축이 진짜 구세주입니다. 이는 1편의 논단과 완전히 일치합니다 — 텍스처가 용량의 80%를 차지해, 최적화의 수익이 가장 높습니다.

한 줄 명령판: 귀찮을 때 한 방 압축

단계별로 가기 싫다면, 모든 최적화를 한 번에:

gltf-transform optimize model.glb model-final.glb \
  --texture-compress basisu \
  --meshopt \
  --simplify --simplify-ratio 0.5 \
  --weld --prune

이 한 줄 명령 = 중복 제거 + 웰드 + 정점 MeshOpt + 텍스처 KTX2 + 지오메트리 단순화. 90% 씬에서 충분하고, 단계별은 주로 이해와 파라미터 조정을 위해서입니다.

자동화 스크립트: 재사용 가능

위 흐름을 빌드에 넣을 수 있는 스크립트로 묶습니다. 이 스크립트는 대상 플랫폼별로 다른 버전을 출력하고 각 단계의 효과를 출력합니다.

// scripts/compress-model.mjs
import { optimize } from "@gltf-transform/functions";
import { NodeIO } from "@gltf-transform/core";
import { KHRONOS_EXTENSIONS } from "@gltf-transform/extensions";
import { filesize } from "filesize";

const io = new NodeIO().registerExtensions(KHRONOS_EXTENSIONS);

// 플랫폼별 압축 전략
const PROFILES = {
  mobile: {
    textureCompression: "basisu",
    meshCompression: "meshopt",
    simplify: { ratio: 0.5 },
    weld: true,
    prune: true,
  },
  vr: {
    textureCompression: "basisu",
    meshCompression: "meshopt",
    // VR 근거리 시청, 단순화 없음
    simplify: null,
    weld: true,
    prune: true,
  },
  desktop: {
    textureCompression: "webp",
    meshCompression: "meshopt",
    simplify: null,
    weld: true,
    prune: true,
  },
};

async function compress(inputPath, profileName) {
  const cfg = PROFILES[profileName];
  const doc = await io.read(inputPath);
  const before = Buffer.byteLength(await io.writeBinary(doc), "utf8");

  await optimize(doc, {
    textureCompression: cfg.textureCompression,
    meshCompression: cfg.meshCompression,
    simplify: cfg.simplify ?? undefined,
    weld: cfg.weld,
    prune: cfg.prune,
  });

  const bytes = await io.writeBinary(doc);
  const outPath = inputPath.replace(/\.glb$/, `-${profileName}.glb`);
  await io.write(outPath, doc);
  const after = bytes.byteLength;

  console.log(
    `${profileName.padEnd(8)} ${filesize(before)} → ${filesize(after)} ` +
      `(${Math.round((1 - after / before) * 100)}% smaller) → ${outPath}`
  );
}

// 사용법: node scripts/compress-model.mjs path/to/model.glb
const input = process.argv[2];
for (const profile of Object.keys(PROFILES)) {
  await compress(input, profile);
}

프로젝트에 넣기:

node scripts/compress-model.mjs public/models/model.glb
# mobile   50MB → 4.5MB (91% smaller) → model-mobile.glb
# vr       50MB → 6.2MB (87% smaller) → model-vr.glb
# desktop  50MB → 11MB (78% smaller)  → model-desktop.glb

프론트엔드는 런타임에 기기별로 대응 버전을 로드하면 됩니다.

이 파이프라인을 직접 구축하고 싶지 않으신가요? Any3D의 온라인 도구가 위의 모든 것을 한 방에 완료합니다 — GLB를 업로드하면 자동으로 텍스처 KTX2 + 정점 MeshOpt를 돌리고, 플랫폼별로 압축 버전을 출력해 로컬 툴체인 구축의 번거로움을 덜어줍니다.

자주 묻는 함정 FAQ

압축 후 모델이 검게 변함 / 텍스처 안 보임

  • 99%는 색공간: 컬러 맵의 sRGB 설정 누락. Three.js에서는 texture.colorSpace = THREE.SRGBColorSpace.
  • toktx에서 컬러 맵에 --srgb 누락.

노멀맵 압축 후 조명이 이상함

  • 노멀맵에 ETC1S 사용, UASTC로 교체.
  • 노멀맵이 DirectX 스타일(그린 채널 다운), 엔진은 OpenGL 스타일 요구, G 채널을 뒤집어야 함.

모바일 로드가 첫 화면에서 멈춤

  • Draco 디코더 wasm을 로드 중인지 확인(추가 요청). 모바일에서는 MeshOpt 우선.
  • KTX2 트랜스코더 경로 설정 오류, 트랜스코드 실패로 CPU 해제로 폴백.

압축 후 파일이 오히려 커짐

  • 맵이 너무 작음(< 128px), KTX2는 손해, 블록 압축에는 고정 오버헤드 있음.
  • 모델이 이미 한 번 압축됨, 재압축은 수익 없음(오히려 마이너스).

단순화 후 모델이 깨짐

  • --simplify-ratio가 너무 낮음, 0.7-0.8로 올리기.
  • 단순화는 하드 서피스(기계, 건축)에 우호적, 유기 곡면(캐릭터)은 깨지기 쉬움.

KTX2가 일부 브라우저에서 로드 실패

  • 구형 Safari / 구형 WebView 미지원. PNG/WebP 폴백을 준비하거나, KHR_texture_basisufallback 필드로 세이프티넷 제공.

시리즈 치트시트

전 6편의 에센스를 하나의 표로 응축, 북마크 추천.

용량 구성

구성비율최적화 도구
텍스처 맵70-85%KTX2(최대 수익)
정점 데이터10-20%MeshOpt / 양자화 / Draco
애니메이션 데이터0-15%키프레임 감소 / 압축
기타< 2%중복 제거

VRAM 공식

전통 포맷 VRAM = 너비 * 높이 * 4바이트 * 1.333(mipmap 포함)
KTX2 블록 압축 VRAM ≈ 위 식 / 4 (ETC1S) 또는 / 2 (UASTC)

정점 압축 선정

시나리오추천
의존 제로, 가장 단순순수 양자화(KHR_mesh_quantization)
웹 밸런스의 첫 선택MeshOpt
극한 압축률, 해독을 기다릴 수 있음Draco
미니프로그램 / 패키지 민감순수 양자화 / MeshOpt, Draco 회피

텍스처 압축 선정

맵 타입추천 인코딩
albedo / emissive(컬러)KTX2 ETC1S
normal / roughness / metallic / AO(데이터)KTX2 UASTC
데스크톱 웹, 다운로드 속도 추구WebP / AVIF
작은 맵(< 128px)PNG 유지, KTX2 안 함

한 방 명령

# 전체 최적화(텍스처 + 정점 + 단순화)
gltf-transform optimize model.glb model-final.glb \
  --texture-compress basisu --meshopt \
  --simplify --simplify-ratio 0.5 --weld --prune

플랫폼 치트

플랫폼텍스처정점
데스크톱 웹WebP / KTX2MeshOpt
모바일 웹KTX2 필수MeshOpt
VRKTX2 필수MeshOpt + LOD
미니프로그램KTX2 / WebPMeshOpt / 양자화
대형 씬KTX2 필수MeshOpt + Draco + LOD

시리즈 회고

여섯 편을 걸어, 묶으면 하나의 완전한 체인이 됩니다:

  1. 왜 이렇게 무거운가: 용량 구성과 VRAM의 진실 파악
  2. 정점 압축의 세 가지 무기: 양자화, MeshOpt, Draco의 원리와 선택
  3. 텍스처의 VRAM 문제: PNG/JPG가 GPU 눈에 죄가 있는 이유
  4. KTX2 실전: ETC1S/UASTC 선정, 툴체인, 엔진 로드
  5. 선정 가이드: 플랫폼/씬별 의사결정 프레임워크
  6. 이 글: 엔드투엔드 파이프라인, Blender에서 출시까지

핵심은 한 문장입니다: 먼저 병목(다운로드/VRAM/프레임레이트)을 생각하고, 그 다음 도구를 고르세요. 텍스처 압축의 수익이 가장 크고, 정점 압축은 보너스입니다. 플랫폼이 다르면 운명도 다릅니다, 일도양단하지 마세요.

이 글의 스크립트와 치트시트를 따라, 모델을 50MB에서 5MB로, VRAM을 520MB에서 70MB로 가져가는 건 재현 가능한 길일 것입니다. 남은 건 그저 손대는 것뿐입니다.

후원하기