Kris

KTX2 실전: 텍스처 압축의 올바른 사용법

3D 압축텍스처 압축KTX2Basis UniversalglTF

이전 글에서 GPU 텍스처 포맷, Basis Universal, KTX2의 관계를 명확히 설명했습니다. 이론은 이해했으니, 이번 글에서는 실전입니다: ETC1S와 UASTC 중 무엇을 선택할지, 어떤 도구를 사용할지, 명령어는 어떻게 입력할지, 엔진에서 어떻게 로드할지에 대해 다룹니다.

그대로 따라 하면서 보면 됩니다.

가장 중요한 선택부터: ETC1S vs UASTC

Basis는 두 가지 중간 인코딩을 제공합니다. 잘못 선택하면 '좋지 않다'는 수준이 아니라, 노멀 맵이 바로 뭉개집니다. 먼저 이 표를 기억하세요:

ETC1SUASTC
압축률매우 높음 (JPG와 유사)중간 (고품질 PNG와 유사)
화질컬러 텍스처에 충분원본에 가까운 품질
VRAM (트랜스코딩 후)보통 4bpp (원본의 약 1/8)보통 8bpp (원본의 약 1/4)
인코딩 속도느림 (조정 가능)비교적 빠름
적합알베도/디퓨즈, 에미시브노멀, 메탈릭-러프니스, 데이터 텍스처
부적합노멀, 정밀한 값이 필요한 텍스처컬러 텍스처 (과잉, 용량 큼)

왜 노멀 맵에 ETC1S를 사용하면 안 될까요? 노멀 맵은 방향 벡터를 저장하며, 각 픽셀의 RGB 세 채널이 서로 제약을 받습니다 (벡터 길이 ≈ 1). ETC1S는 '색상이 보기에 좋게' 설계된 블록 압축으로, 단일 채널 정밀도에 민감하지 않습니다. 압축 후 벡터 방향이 틀어져 조명이 즉시 부자연스러워집니다. 특히 하이라이트 부분과 고주파 디테일에서 문제가 발생합니다. UASTC는 수치를 더 잘 보존하여 이러한 정밀도 요구를 견딜 수 있습니다.

실용 규칙:

  • 컬러 텍스처 (알베도, 에미시브) → ETC1S
  • 데이터 텍스처 (노멀, 러프니스, 메탈릭, AO, 두께) → UASTC
  • 확실하지 않고 용량을 줄이고 싶다면 → 먼저 ETC1S로 시도하고, 특정 부분이 뭉개지면 UASTC로 변경

같은 이미지, 네 가지 포맷 비교

2048×2048 알베도 텍스처를 기준으로 (데이터는 커뮤니티 대표값, 참고용):

포맷디스크 크기VRAM 사용량 (밉맵 포함)GPU 업로드크로스 플랫폼
PNG~5MB~22MB느림
WebP~1MB~22MB느림
KTX2 (ETC1S)~0.5-0.8MB~2.8MB빠름
KTX2 (UASTC)~3-4MB~5.6MB빠름

WebP의 VRAM 사용량은 PNG와 동일합니다. 디스크만 작을 뿐, VRAM에 로드되면 원본 픽셀로 디코딩됩니다. KTX2의 ETC1S는 디스크와 VRAM을 동시에 낮춰주므로 그 가치가 있습니다.

도구 체인: 세 가지 경로 모두 가능

KTX2를 압축하는 도구는 하나만 있는 것이 아닙니다. '편리함' 순으로 나열합니다:

1. toktx (공식, 가장 강력함)

Khronos 공식 도구로, 매개변수가 가장 많으며 개별 텍스처 처리에 적합합니다.

# PNG를 ETC1S 인코딩 KTX2로 변환
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png

# PNG를 UASTC 인코딩 KTX2로 변환
toktx --uastc 1 normal.ktx2 normal.png

자주 사용하는 매개변수:

# ETC1S + 품질 레벨 (1-255, 기본 128, 높을수록 화질↑ 용량↑)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png

# UASTC + 슈퍼 압축 (Zstandard, 디스크 크기 추가 감소)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png

# 밉맵 자동 생성 (강력 권장)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png

# sRGB 색 공간 지정 (컬러 텍스처 필수)
toktx --bcmp --srgb albedo.ktx2 albedo.png

--bcmp는 ETC1S 모드 스위치 (Basis Universal의 기본 모드), --uastc 1은 UASTC 모드입니다. 둘은 상호 배타적입니다.

2. gltf-transform (가장 간편, 강력 권장)

glTF/GLB 모델 전체를 다룰 때는 gltf-transform 한 줄 명령으로 모든 텍스처를 KTX2로 변환하고, 텍스처 용도에 따라 자동으로 ETC1S/UASTC를 선택합니다.

# 설치
npm install -g @gltf-transform/cli

# 모델 전체를 한 번에 압축
gltf-transform optimize model.glb model-optimized.glb \
  --texture-compress basisu

내부적으로 텍스처 용도를 판단합니다: 컬러 계열은 ETC1S, 데이터 계열은 UASTC, 그리고 자동으로 KHR_texture_basisu 확장을 작성합니다. 90%의 시나리오에서 최선의 선택이며, 개별적으로 toktx를 입력할 필요가 없습니다.

3. 온라인 도구 (가장 빠른 시작)

환경을 설치하고 싶지 않을 때는 브라우저 도구를 사용하세요: gltf.report (온라인 gltf-transform), KTX2 Converter 등. 업로드, 다운로드, 끝. 초기 테스트나 일회성 작업에 적합합니다.

완전한 파이프라인: 소스 파일에서 서비스까지

표준 프로세스를 정리합니다 (순서도):

소스 파일(PNG/JPG/PSD/TGA)
        │
        ├── [전체 모델] gltf-transform optimize model.glb → 텍스처 유형 자동 인식
        │            └─ 컬러 텍스처 → ETC1S
        │            └─ 데이터 텍스처 → UASTC
        │            └─ KHR_texture_basisu 확장 기록
        │
        └── [개별 텍스처] toktx → 수동으로 ETC1S/UASTC + 색 공간 + 밉맵 지정
        │
        ▼
압축된 KTX2 / GLB
        │
        ▼
엔진 로딩 (Three.js / Babylon.js) ── 런타임 트랜스코딩 → GPU 네이티브 포맷

Three.js에서 KTX2 로드

Three.js는 r129부터 KTX2를 기본 지원합니다: KTX2Loader (basis 트랜스코더 wasm 경로 지정, GPU 기능 탐지)를 GLTFLoader에 연결한 후, KTX2 텍스처를 포함한 glTF를 로드하면 자동으로 트랜스코딩됩니다.

const ktx2Loader = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
gltfLoader.setKTX2Loader(ktx2Loader); // 모델이 Draco/MeshOpt를 함께 사용한다면 DRACOLoader/MeshoptDecoder도 연결

detectSupport(renderer)는 반드시 호출해야 합니다. 런타임 시 어떤 네이티브 포맷으로 트랜스코딩할지 결정합니다. 또한 컬러 텍스처는 texture.colorSpace = THREE.SRGBColorSpace로 설정해야 합니다. 설정하지 않으면 '전체 이미지가 회색으로 보이는' 전형적인 함정에 빠집니다 (노멀/러프니스 등 데이터 텍스처는 선형 유지).

Babylon.js에서 KTX2 로드

Babylon.js는 더 간편합니다. GLTFFileLoader는 기본적으로 KHR_texture_basisu를 활성화하며, CDN에서 basis 트랜스코더를 자동으로 가져와 KTX2를 포함한 glTF를 즉시 로드할 수 있습니다. 오프라인/내부 네트워크 환경에서는 BASISFileLoader.TranscoderModule을 수동으로 설정해야 합니다.

압축 매개변수 튜닝: 품질과 용량의 균형

ETC1S의 핵심 매개변수는 --qlevel (1-255)입니다. 결과에 미치는 영향:

qlevel용량화질인코딩 시간적합
128 (기본)작음충분함중간대부분의 경우
200-255다소 큼거의 무손실길어짐 (수배)고품질 요구
60-100매우 작음눈에 띄는 블록빠름원경/작은 텍스처

UASTC의 용량은 상대적으로 고정되어 있으며, 주로 --zcmp (Zstandard 슈퍼 압축)로 디스크 크기를 조정합니다. VRAM에는 영향을 미치지 않습니다 (압축 해제 후 여전히 8bpp).

튜닝 순서 권장:

  1. 기본 매개변수로 먼저 압축하고 용량과 화질 확인
  2. 만족스럽지 않으면 → --qlevel (ETC1S) 조정 또는 --zcmp (UASTC) 추가
  3. 노멀 맵이 뭉개지면 → UASTC를 사용 중인지 확인, ETC1S가 아님
  4. 색상이 어둡게 보이면 → 색 공간 설정 확인 (sRGB 플래그, 엔진의 colorSpace)

자주 발생하는 문제 해결

트랜스코딩 실패 / 로딩 오류

  • 트랜스코더 wasm 경로가 올바른지 확인 (Three.js는 setTranscoderPath 필요)
  • 엔진 버전이 현재 KTX2 버전을 지원하는지 확인 (구형 basis 인코딩은 신형 트랜스코더에서 지원되지 않음)
  • 콘솔에 일반적으로 구체적인 오류가 표시되므로 키워드로 검색

색상이 어둡거나 밝게 보임

  • 컬러 텍스처(알베도)에 sRGB가 설정되지 않았거나 반대로 설정됨
  • toktx 시 --srgb 누락 (컬러 텍스처는 추가 필요)
  • 데이터 텍스처(노멀)에 sRGB가 잘못 추가됨

밉맵 누락, 원거리에서 깜빡임

  • 압축 시 --genmipmap 누락
  • 엔진에서 texture.generateMipmaps가 꺼져 있음 (Three.js에서 KTX2는 파일에 기본 포함되지만, 재질의 minFilter가 밉맵 모드를 사용해야 함)

특정 부분에서 노멀 방향이 틀어짐

  • 노멀 맵이 ETC1S로 인코딩됨 → UASTC로 변경
  • 노멀 맵이 OpenGL 스타일(녹색 채널 위쪽)인지 확인, DirectX 스타일은 일부 엔진에서 G 채널을 뒤집어야 함

파일이 오히려 커짐

  • 작은 크기의 텍스처(< 128×128)는 KTX2가 비효율적입니다. 블록 압축은 고정 오버헤드가 있어 블록 전체를 채우게 됩니다.
  • 단색 텍스처는 KTX2로 압축하지 말고, 재질 색상 값을 직접 사용하는 것이 더 효율적입니다.

다음 단계

텍스처 압축에 관한 이 부분은 이제 기본적으로 끝

후원하기