給模型減肥的第一課:頂點壓縮三板斧
上一篇我們拆開了一個 GLB 檔案,看到貼圖吃掉 80% 的體積,頂點只佔 10-20%。所以頂點壓縮看起來是不是無關緊要?
恰恰相反。當一個模型的貼圖已經壓到 KTX2、頂點又密密麻麻時,剩下的 20% 就是頂點——而這 20% 可以再砍掉一半甚至 90%。更重要的是,頂點壓縮是少數幾種幾乎零代價、立即生效的最佳化:加幾行命令、換一個解碼器,檔案就瘦下來了。
這一篇講清楚三件事:頂點資料到底長什麼樣;量化、MeshOpt、Draco 三者各自的脾氣;以及一個讓你少踩坑的結論——沒有「最好的」方案,只有「最合適的」方案。
一個頂點到底有多大
先看一個頂點裡裝了什麼。在 glTF 裡,每個頂點由若干屬性(attribute)組成:
| 屬性 | 用途 | 預設精度 | 每頂點位元組數 |
|---|---|---|---|
| position(位置) | 頂點在空間中的座標 | 3 × float32 | 12 |
| normal(法線) | 決定光照方向 | 3 × float32 | 12 |
| tangent(切線) | 法線貼圖計算 | 4 × float32 | 16 |
| texcoord_0(UV) | 貼圖取樣座標 | 2 × float32 | 8 |
| color(頂點色) | 頂點層級的著色 | 4 × float32 | 16 |
一個帶 PBR 完整屬性的頂點,光幾何資料就要 48-64 位元組。一個 10 萬頂點的模型,光頂點就是 5-6MB。
注意這裡幾乎全部用的是 float32(32 位元浮點數)。這是預設配置,也是頂點壓縮的突破口——因為絕大多數屬性根本用不到 32 位的精度。
第一板斧:量化(Quantization)
量化是所有頂點壓縮的底層原理,Draco 和 MeshOpt 內部也都在用它。
quantization(量化,把高精度的浮點數映射到低精度的整數) 的本質是:浮點數 3.14159265,你記住 3.14 就夠用了。空間範圍內的一組座標,你不再用 32 位精確記錄每個小數,而是用一個範圍更小的整數來表示。
原始: position.x = 1.234567 (float32, 4位元組)
量化後: position.x = 1234 (int16, 2位元組) + 一個 scale/offset 還原
量化前後的對比:
| 屬性 | float32 位元組數 | 量化後(16 位) | 節省 |
|---|---|---|---|
| position | 12 | 6 | 50% |
| normal | 12 | 6(或 4,用 int8 + octahedral) | 50-67% |
| tangent | 16 | 4-8 | 50-75% |
| texcoord | 8 | 4 | 50% |
對剛才那個 48-64 位元組的頂點,量化後基本能壓到 16-24 位元組,體積直接減半以上。
什麼時候用量化
- 你只想減小體積、不需要極致壓縮率
- 你希望零解碼器依賴——量化後的 glTF 用標準的
KHR_mesh_quantization擴展,主流引擎原生支援,不需要額外引入解碼庫 - 目標平台對包體敏感(比如微信小程式,多帶一個 Draco 解碼器就是幾十 KB)
什麼時候別用
- 模型本身極小、細節是賣點(比如毫米級的工業零件)。量化對小尺寸模型特別容易露出馬腳——貼圖還行,但頂點位置偏移 0.1mm 在特寫鏡頭下肉眼可見。
量化精度損失的真實坑:一個珠寶展示場景,把戒指模型量化到 16 位後,特寫鏡頭下金屬邊緣出現了鋸齒。原因不是頂點數不夠,而是世界座標系太小,16 位整數能表達的範圍不夠細。解法是降低量化範圍(縮小
position的 bounding box)或對小模型改用更高位深。
第二板斧:MeshOpt
MeshOpt 是 glTF 官方擴展 EXT_meshopt_compression,定位是「壓縮率不錯、解碼飛快」。
它的做法是先對屬性做量化(和上面一樣),再用一種叫 熵編碼(lossless,無損) 的手段把量化後的整數進一步無損壓一遍。換句話說:量化有損 + 熵編碼無損 = 體積更小、畫質和量化一致。
- 壓縮率:比單純量化再小 30-50%
- 解碼速度:極快,純 C/JS 實現,單執行緒每秒解幾千萬頂點
- 解碼器體積:很小(gzip 後約 20-30KB)
- 相容性:Three.js、Babylon.js 原生支援,是 Web 端事實標準之一
什麼時候用 MeshOpt
- 需要更高壓縮率,又不能接受 Draco 那種較慢的解碼
- Web 端為主、行動端、WebXR——解碼速度直接關係到首屏載入體驗
- 模型需要頻繁解壓縮(比如動態載入的關卡)
什麼時候別用
- 你的目標平台連
EXT_meshopt_compression都不認(極少數老舊引擎) - 你只需要「能跑」、不在乎 30% 的差距——那純量化更簡單,少一個依賴
第三板斧:Draco
Draco 是 Google 出的壓縮方案,定位是「極限壓縮率」。
它和前兩者的根本區別:Draco 會改變頂點的連接關係(拓撲結構)。量化只改每個頂點的數值表示,MeshOpt 在此基礎上做無損編碼,而 Draco 會重新組織三角網格,用更緊湊的方式表達「哪些頂點連成三角形」。
- 壓縮率:三者最高,頂點密集的模型常能做到 90% 以上的縮減
- 解碼速度:三者最慢,但仍然很快(只是相對慢)
- 解碼器體積:較大(約 100-200KB,且通常要單獨載入 wasm)
- 畫質:可調,但極端壓縮率下會有可見形變
什麼時候用 Draco
- 模型極大、頂點超密(百萬級頂點掃描模型、地形)
- 一次性載入、解壓後長時間複用(解碼慢一點能接受)
- 包體不是瓶頸,下載速度才是
什麼時候別用
- 行動端 + 需要快速首屏——解碼器和模型本身都要下載,反而拖慢
- 小程式等對包體嚴苛的環境
- 模型需要做蒙皮動畫、變形目標(morph targets)——Draco 對這些支援較弱,配置不當會出問題
三者放一起:一張表選型
以下壓縮比參考自社群基準測試(DeepKolos 知乎測評 + Reddit r/threejs 討論),不同模型會有出入,但相對關係基本穩定:
| 方案 | 壓縮比(相對 float32) | 解碼速度 | 解碼器體積 | 有損嗎 | glTF 擴展 |
|---|---|---|---|---|---|
| 純量化 | ~50% | 原生,無需解碼 | 0 | 是(精度) | KHR_mesh_quantization |
| MeshOpt | ~25-35% | 極快 | ~25KB | 是(精度) | EXT_meshopt_compression |
| Draco | ~10-20% | 快(三者中最慢) | ~100-200KB | 是(精度+拓撲) | KHR_draco_mesh_compression |
解碼器與平台相容性:
| 平台 | 純量化 | MeshOpt | Draco |
|---|---|---|---|
| 桌面 Web | ✅ 原生 | ✅ 原生 | ✅ 需配解碼器 |
| 行動 Web | ✅ 原生 | ✅ 原生 | ⚠️ 解碼器偏重 |
| WebXR/VR | ✅ 原生 | ✅ 推薦 | ⚠️ 謹慎 |
| 微信小程式 | ✅ 推薦 | ✅ 推薦 | ❌ 盡量避免 |
一句話總結:要省心、零依賴 → 純量化;要均衡 → MeshOpt;要極致壓縮率且能等 → Draco。
實操:用 gltfpack 做量化和 MeshOpt
gltfpack 是 glTF 官方工具,一行命令搞定量化和 MeshOpt。
先安裝(二進位可從 gltfpack release 下載):
# 把 model.glb 量化到 16 位,並加上 MeshOpt 壓縮
gltfpack -i model.glb -o model-packed.glb -cc
# -cc = compress(在預設量化之上疊加 EXT_meshopt_compression)
常用參數:
# 只量化,不開 MeshOpt(最輕量、零解碼器依賴)
# gltfpack 預設就對頂點做 16 位量化(KHR_mesh_quantization),不加選項即可
gltfpack -i model.glb -o model-quant.glb
# 量化並啟用 MeshOpt
gltfpack -i model.glb -o model-meshopt.glb -cc
# 頂點非常多時,可順手簡化(減少頂點數,會改變模型)
gltfpack -i model.glb -o model-simplify.glb -cc -si 0.5
# -si 0.5 表示簡化到約 50% 頂點
關於
-cc:它是「compress」開關,會額外疊加EXT_meshopt_compression。不寫-cc時 gltfpack 預設就做量化——也就是說gltfpack -i in.glb -o out.glb這條命令本身已經是「純量化、零解碼器依賴」。(-v是 verbose 詳細日誌開關,不要混淆。)
典型效果(一個 5MB、12 萬頂點的 PBR 模型,僅供參考):
| 處理 | 檔案大小 | 說明 |
|---|---|---|
| 原始(float32) | 5.0MB | 基準 |
| 純量化(預設) | 2.6MB | 減半,視覺無明顯差異 |
MeshOpt(-cc) | 1.7MB | 再省 35%,載入稍快 |
注意:
-si簡化是改變模型幾何的有損操作,和壓縮是兩回事。壓縮盡量保持視覺一致,簡化則是主動減少細節。兩者可以疊加,但要看場景是否允許。
常見踩坑
- 量化後法線方向變了:多半是用了過低精度。normal 至少用 16 位,或用八面體編碼(octahedral)的 8 位。
- Draco 解碼後材質丟失:Draco 只壓縮網格,材質和貼圖要單獨處理。載入時需要同時配好 Draco 解碼器和 KHR 擴展。
- 小程式裡 Draco 載入不出來:解碼器 wasm 在某些執行時環境裡載入受限,換成 MeshOpt 通常就好了。
- 量化後模型「漂移」:模型離座標原點太遠時,16 位精度不足以表達大座標 + 小細節。解法是把模型移到原點附近再量化,或加大位深。
下一步
頂點壓完了,但別急著慶祝——前面說過,貼圖佔模型 80% 的體積。下一篇我們換個戰場,看看為什麼傳統的 PNG/JPG 在 GPU 眼裡是個「大胃王」,以及 GPU 原生貼圖格式是怎麼解決這個問題的。