Any3DAny3D
·Any3D Team

給模型減肥的第一課:頂點壓縮三板斧

3d-compressionvertex-compressionmeshoptdracogltf

上一篇我們拆開了一個 GLB 檔案,看到貼圖吃掉 80% 的體積,頂點只佔 10-20%。所以頂點壓縮看起來是不是無關緊要?

恰恰相反。當一個模型的貼圖已經壓到 KTX2、頂點又密密麻麻時,剩下的 20% 就是頂點——而這 20% 可以再砍掉一半甚至 90%。更重要的是,頂點壓縮是少數幾種幾乎零代價、立即生效的最佳化:加幾行命令、換一個解碼器,檔案就瘦下來了。

這一篇講清楚三件事:頂點資料到底長什麼樣;量化、MeshOpt、Draco 三者各自的脾氣;以及一個讓你少踩坑的結論——沒有「最好的」方案,只有「最合適的」方案

一個頂點到底有多大

先看一個頂點裡裝了什麼。在 glTF 裡,每個頂點由若干屬性(attribute)組成:

屬性用途預設精度每頂點位元組數
position(位置)頂點在空間中的座標3 × float3212
normal(法線)決定光照方向3 × float3212
tangent(切線)法線貼圖計算4 × float3216
texcoord_0(UV)貼圖取樣座標2 × float328
color(頂點色)頂點層級的著色4 × float3216

一個帶 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 位)節省
position12650%
normal126(或 4,用 int8 + octahedral)50-67%
tangent164-850-75%
texcoord8450%

對剛才那個 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

解碼器與平台相容性:

平台純量化MeshOptDraco
桌面 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(-cc1.7MB再省 35%,載入稍快

注意:-si 簡化是改變模型幾何的有損操作,和壓縮是兩回事。壓縮盡量保持視覺一致,簡化則是主動減少細節。兩者可以疊加,但要看場景是否允許。

常見踩坑

  • 量化後法線方向變了:多半是用了過低精度。normal 至少用 16 位,或用八面體編碼(octahedral)的 8 位。
  • Draco 解碼後材質丟失:Draco 只壓縮網格,材質和貼圖要單獨處理。載入時需要同時配好 Draco 解碼器和 KHR 擴展。
  • 小程式裡 Draco 載入不出來:解碼器 wasm 在某些執行時環境裡載入受限,換成 MeshOpt 通常就好了。
  • 量化後模型「漂移」:模型離座標原點太遠時,16 位精度不足以表達大座標 + 小細節。解法是把模型移到原點附近再量化,或加大位深。

下一步

頂點壓完了,但別急著慶祝——前面說過,貼圖佔模型 80% 的體積。下一篇我們換個戰場,看看為什麼傳統的 PNG/JPG 在 GPU 眼裡是個「大胃王」,以及 GPU 原生貼圖格式是怎麼解決這個問題的。

贊助支持