KTX2 實戰:貼圖壓縮的正確打開方式
上一篇把 GPU 貼圖格式、Basis Universal、KTX2 的關係講清楚了。道理懂了,這篇全是動手:ETC1S 和 UASTC 怎麼選、用什麼工具、命令怎麼敲、引擎裡怎麼載入。
可以照著抄,邊看邊做。
先解決最重要的一個選擇:ETC1S 還是 UASTC
Basis 提供兩種中間編碼,選錯了不是「不夠好」的問題,而是法線貼圖直接糊掉。先把這張表記住:
| ETC1S | UASTC | |
|---|---|---|
| 壓縮率 | 極高(類似 JPG) | 中等(類似高品質 PNG) |
| 畫質 | 顏色類貼圖夠用 | 接近原始品質 |
| 顯存(轉碼後) | 通常 4bpp(約 1/8 原始) | 通常 8bpp(約 1/4 原始) |
| 編碼速度 | 慢(可調級別) | 較快 |
| 適用 | albedo/漫反射、emissive | normal、metalness-roughness、資料貼圖 |
| 不適用 | normal、需要精確數值的圖 | 顏色貼圖(殺雞用牛刀,體積偏大) |
為什麼法線貼圖不能用 ETC1S?因為法線貼圖存的是方向向量,每個像素的 RGB 三個通道互相約束(向量長度 ≈ 1)。ETC1S 是為「顏色看起來對」設計的塊壓縮,它對單通道精度不敏感,壓完之後向量方向會偏,光照立刻顯得不對——尤其是高光位置和高頻細節。UASTC 對數值保留得更好,能扛住這種精度要求。
實用規則:
- 顏色貼圖(albedo、emissive)→ ETC1S
- 資料貼圖(normal、roughness、metallic、AO、thickness)→ UASTC
- 拿不準、又想省體積 → 先試 ETC1S,特寫下糊了再換 UASTC
同一張圖,四種格式比一比
用一張 2048×2048 的 albedo 貼圖做基準(資料為社群典型值,僅供參考):
| 格式 | 磁碟大小 | 顯存佔用(含 mipmap) | 上傳到 GPU | 跨平台 |
|---|---|---|---|---|
| PNG | ~5MB | ~22MB | 慢 | ✅ |
| WebP | ~1MB | ~22MB | 慢 | ✅ |
| KTX2 (ETC1S) | ~0.5-0.8MB | ~2.8MB | 快 | ✅ |
| KTX2 (UASTC) | ~3-4MB | ~5.6MB | 快 | ✅ |
注意 WebP 的顯存佔用和 PNG 一樣——它只是磁碟小,進了顯存照樣解壓縮成原始像素。KTX2 的 ETC1S 把磁碟和顯存同時打到很低,這就是它值錢的地方。
工具鏈:三條路都通
壓縮 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
# 自動產生 mipmap(強烈建議加)
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 等。上傳,下載,完事。適合早期試驗或一次性任務。
完整 pipeline:從原始檔到上線
整理一下標準流程(流程圖):
原始檔(PNG/JPG/PSD/TGA)
│
├── [整模型] gltf-transform optimize model.glb → 自動識別貼圖類型
│ └─ 顏色貼圖 → ETC1S
│ └─ 資料貼圖 → UASTC
│ └─ 寫入 KHR_texture_basisu 擴展
│
└── [單張貼圖] toktx → 手動指定 ETC1S/UASTC + 色彩空間 + mipmap
│
▼
壓縮後的 KTX2 / GLB
│
▼
引擎載入(Three.js / Babylon.js)── 執行時轉碼 → GPU 原生格式
Three.js 裡載入 KTX2
Three.js 從 r129 起原生支援 KTX2,但需要提供 KTX2Loader 並設定轉碼器(basis transcoder wasm)。
import * as THREE from "three";
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
// 1. 初始化 KTX2 轉碼器,探測當前 GPU 支援哪種原生格式
const ktx2Loader = new KTX2Loader()
.setTranscoderPath("/basis/") // basis transcoder wasm 所在目錄
.detectSupport(renderer); // 必須傳入 renderer 探測能力
// 2. 設定 GLTFLoader,把 KTX2/Draco/MeshOpt 都掛上
const gltfLoader = new GLTFLoader();
gltfLoader.setKTX2Loader(ktx2Loader);
gltfLoader.setDRACOLoader(
new DRACOLoader().setDecoderPath("/draco/")
);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
// 3. 載入模型,貼圖會自動轉碼
gltfLoader.load("/models/model-optimized.glb", (gltf) => {
scene.add(gltf.scene);
});
幾個關鍵點:
detectSupport(renderer)必須調,它決定了執行時轉碼成哪種原生格式setTranscoderPath指向 basis transcoder 的 wasm 檔案(從 basis_transcoder release 複製到 public 目錄)- 如果模型同時用了 Draco,記得把 DRACOLoader 也掛上;用了 MeshOpt 就掛 MeshoptDecoder
單獨載入一張 KTX2 貼圖:
const texture = await ktx2Loader.loadAsync("/textures/albedo.ktx2");
texture.colorSpace = THREE.SRGBColorSpace; // 顏色貼圖設 sRGB
material.map = texture;
最常見的坑就在這一步:顏色貼圖忘了設
colorSpace = SRGBColorSpace,整張圖發灰發暗。資料貼圖(normal/roughness)則保持NoColorSpace(線性),別搞反。
Babylon.js 裡載入 KTX2
Babylon 的做法類似,但更自動——KHR_texture_basisu 在 GLTFFileLoader 裡預設開啟,只要保證 transcoder 檔案能被找到。
import { Scene } from "@babylonjs/core/scene";
import { Engine } from "@babylonjs/core/Engines/engine";
import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader";
import { GLTFFileLoader } from "@babylonjs/loaders/glTF/glTFFileLoader";
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu";
const engine = new Engine(canvas);
const scene = new Scene(engine);
SceneLoader.ImportMesh(
"",
"/models/",
"model-optimized.glb",
scene,
(meshes) => {
// 模型載入完成,KTX2 已自動轉碼
}
);
Babylon 會自動從 CDN 拉取 basis transcoder,離線/內網環境需要手動設定 BASISFileLoader.TranscoderModule。
壓縮參數調優:品質與體積的平衡
ETC1S 的核心參數是 --qlevel(1-255)。它怎麼影響結果:
| qlevel | 體積 | 畫質 | 編碼耗時 | 適用 |
|---|---|---|---|---|
| 128(預設) | 小 | 夠用 | 中 | 多數情況 |
| 200-255 | 偏大 | 接近無損 | 長(數倍) | 高品質要求 |
| 60-100 | 很小 | 有可見塊狀 | 快 | 遠景/小貼圖 |
UASTC 體積相對固定,主要靠 --zcmp(Zstandard 超級壓縮)調磁碟大小,不影響顯存(解壓縮後還是 8bpp)。
調優順序建議:
- 先用預設參數壓一遍,看體積和畫質
- 不滿意 → 調
--qlevel(ETC1S)或加--zcmp(UASTC) - 法線貼圖糊了 → 確認用的是 UASTC,不是 ETC1S
- 顏色偏暗 → 檢查色彩空間設定(sRGB 標誌、引擎裡的 colorSpace)
常見問題排查
轉碼失敗 / 載入報錯
- 檢查 transcoder wasm 路徑是否正確(Three.js 要
setTranscoderPath) - 檢查引擎版本是否支援當前 KTX2 版本(舊版 basis 編碼不被新版 transcoder 支援)
- 控制台通常會有具體錯誤,按關鍵字搜
顏色偏暗 / 偏亮
- 顏色貼圖(albedo)沒設 sRGB,或設反了
- toktx 時漏了
--srgb(顏色貼圖要加) - 資料貼圖(normal)誤加了 sRGB
mipmap 缺失,遠處閃爍
- 壓縮時沒加
--genmipmap - 引擎裡
texture.generateMipmaps沒開(Three.js 裡 KTX2 預設隨檔案,但仍需材質minFilter用 mipmap 模式)
特寫下法線方向不對
- 法線貼圖用了 ETC1S,換 UASTC
- 確認法線貼圖是 OpenGL 風格(綠色通道朝上),DirectX 風格在某些引擎裡要翻轉 G 通道
檔案反而變大了
- 小尺寸貼圖(< 128×128)用 KTX2 不划算,塊壓縮有固定開銷,會佔滿整個塊
- 純色貼圖別壓 KTX2,直接用材質色值更省
下一步
貼圖壓縮這塊到這就基本通關了。但「會用工具」不等於「會用對工具」——下一篇把前 4 篇的所有知識匯總成一個選型框架:桌面、行動、VR、小程式,每種場景到底該用哪種組合。