KTX2 실전: 텍스처 압축의 올바른 사용법
이전 글에서 GPU 텍스처 포맷, Basis Universal, KTX2의 관계를 명확히 설명했습니다. 이론은 이해했으니, 이번 글에서는 실전입니다: ETC1S와 UASTC 중 무엇을 선택할지, 어떤 도구를 사용할지, 명령어는 어떻게 입력할지, 엔진에서 어떻게 로드할지에 대해 다룹니다.
그대로 따라 하면서 보면 됩니다.
가장 중요한 선택부터: ETC1S vs UASTC
Basis는 두 가지 중간 인코딩을 제공합니다. 잘못 선택하면 '좋지 않다'는 수준이 아니라, 노멀 맵이 바로 뭉개집니다. 먼저 이 표를 기억하세요:
| ETC1S | UASTC | |
|---|---|---|
| 압축률 | 매우 높음 (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).
튜닝 순서 권장:
- 기본 매개변수로 먼저 압축하고 용량과 화질 확인
- 만족스럽지 않으면 →
--qlevel(ETC1S) 조정 또는--zcmp(UASTC) 추가 - 노멀 맵이 뭉개지면 → UASTC를 사용 중인지 확인, ETC1S가 아님
- 색상이 어둡게 보이면 → 색 공간 설정 확인 (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로 압축하지 말고, 재질 색상 값을 직접 사용하는 것이 더 효율적입니다.
다음 단계
텍스처 압축에 관한 이 부분은 이제 기본적으로 끝