引言
最近做T級互動,需要使用到3D模型。相信大家和我一樣,在開始着手的時候,一定會有這麼些問題:
- 1. 如何選擇3D模型的導出格式
- 2. 如何對模型檔案進行優化
- 3. 在大流量的項目中相容性怎麼樣
讓我們通過這篇文章,進行細緻的探索、調研與沉澱。
一、什麼是 glTF 檔案
glTF[1] 全稱 Graphics Language Transmission Format,是三維場景和模型的标準檔案格式。
glTF 核心是 JSON 檔案,描述了 3D 場景的整個内容。它由場景結構本身的描述組成,其由定義場景圖的節點的層次提供。
場景中出現的 3D 對象是使用連接配接到節點的 meshes(網格)定義的。Materials(材料)定義對象的外觀。Animations(動畫)描述 3D 對象如何随着時間的推移轉換 3D 對象,并且 Skins(蒙皮)定義了對物體的幾何形狀的方式基于骨架姿勢變形。Cameras(相機)描述了渲染器的視圖配置。
除此以外,它還包括了帶有二進制資料和圖像檔案的連結,如下圖所示。
二、.gltf 與.glb
從 blender 檔案導出中可以看出:
glTF 檔案有兩種拓展形式,.gltf(JSON / ASCII)或.glb(二進制)。.gltf 檔案可能是自包含的,也可能引用外部二進制和紋理資源,而 .glb 檔案則是完全自包含的(但使用外部工具可以将其緩沖區/紋理儲存為嵌入或單獨的檔案,後面會提到)。
2.1 .glb檔案産生原因—
glTF 提供了兩個也可以一起使用的傳遞選項:
- glTF JSON 指向外部二進制資料(幾何、關鍵幀、皮膚)和圖像。
- glTF JSON 嵌入 base64 編碼的二進制資料,并使用資料 URI 内聯圖像。
對于這些資源,由于 base64 編碼,glTF 需要單獨的請求或額外的空間。Base64 編碼需要額外的處理來解碼并增加檔案大小(編碼資源增加約 33%)。雖然 gzip 減輕了檔案大小的增加,但解壓縮和解碼仍然會增加大量的加載時間。
為了解決這個問題,引入了一種容器格式 Binary glTF。在二進制 glTF 中,glTF 資産(JSON、.bin 和圖像)可以存儲在二進制 blob 中,就是.glb 檔案[2]。
2.2 檔案對比—
2.2.1 同一個glTF檔案,.glb格式要比.gltf小
- 自包含的:
- 引用外部二進制和紋理資源的:
2.2.2 .gltf檔案預覽:
- 自包含的:
- 引用外部二進制和紋理資源:
2.2.3 glb檔案預覽:
- 自包含的:
- 引用外部二進制和紋理資源:
從圖中可以看到,當非自包含型的時候,請求glTF檔案時,會一同請求圖檔檔案。
那麼,我們就可以利用這個特性,就可以實作一些性能優化,讓我們往下繼續。
三、glTF 檔案拆分
上文提到,glTF檔案可以拆分為.gltf/.glb檔案+二進制檔案+紋理圖檔,那麼,我們就可以将其拆分出來,并對紋理圖檔進行單獨的壓縮,來進行性能的優化。
可以使用gltf pipeLine ,其具有以下功能:
- glTF 與 glb 的互相轉換
- 将緩沖區/紋理儲存為嵌入或單獨的檔案
- 将 glTF 1.0 模型轉換為 glTF 2.0(使用KHR_techniques_webgl和KHR_blend)
- 使用 Draco 進行網格壓縮
在這裡,我們是要使用“将緩沖區/紋理儲存為嵌入或單獨的檔案”這個功能。
讓我們來看看拆分出來的檔案
再回顧一下,.glb檔案是這麼引入外部單獨的紋理與二進制檔案的
是以,隻要将拆分出來的這幾個檔案,放入同一個路徑中,然後像之前那樣引入就好了。
- 壓縮方式
gltf-pipeline -i male.glb -o male-processed.glb -s
- 使用方式(在 Three.js 中) 普普通通地用就好了,和不拆分的沒什麼差別
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
const loader = new GLTFLoader()
loader.load(MODEL_FILE_PATH, (gltf) => {
// ....
})
- 性能對比
四、glTF 檔案壓縮
如上面介紹,glTF 檔案包括.gltf/.glb 檔案、.bin 檔案以及紋理資源。glTF2.0 相關的插件主要有以下:
那麼我們從中取一些來分析一下。
4.1 網格壓縮—
4.1.1KHR_draco_mesh_compression[3]
最常見的一種網格壓縮方式,采用開源的Draco算法,用于壓縮和解壓縮3D 網格和點雲,并且可能會改變網格中頂點的順序和數量。壓縮的使檔案小得多,但是在用戶端裝置上需要額外的解碼時間。
- 壓縮方式
可以使用gltf-pipelinegltf 檔案優化工具進行壓縮
gltf-pipeline -i male.glb -o male-processed.glb -d
- 使用方式(在 Three.js 中)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
const loader = new GLTFLoader()
// 建立解碼器執行個體
const dracoLoader = new DRACOLoader()
// 設定解壓庫檔案路徑
dracoLoader.setDecoderPath(DECODER_PATH)
// 加載解碼器執行個體
loader.setDRACOLoader(dracoLoader)
loader.load(MODEL_FILE_PATH, (gltf) => {
// ....
})
- 性能分析對比
這個 glb 檔案原大小為 3.2M,draco 壓縮後為 1.8M,約為原檔案的56%。
從上面的代碼中可以看出,建立解碼器執行個體需要引入額外的庫來進行解碼,setDecoderPath會自動請求 wasm 檔案來進行解密操作。而這兩個 wasm 檔案同時也增加了請求時間和請求數量,那麼加上這兩個檔案,真實的壓縮率約為62.5%。
是以,如果一個項目需要加載多個 glTF 檔案,那麼可以建立一個 DRACOLoader 執行個體并重複使用它。但如果項目隻需要加載一個 glTF 檔案,那麼使用 draco 算法是否具有“成本效益”就值得考量了。
用 demo 進行一下性能對比:
可見 draco 算法首次加載和解密時間,要大于原檔案。而在實際項目中,這個差距更加明顯,并且偶爾會出現解密堵塞的情況,需要重新進入頁面才能恢複功能。
除此以外,還有一個很直覺的問題,模型畫質的損失是肉眼可觀的。
如圖,分别是在 iPhone 12 和小米 MIX2 中的樣子:
總而言之,如果要将 draco 壓縮算法運用到大規模項目中,需要結合實際項目進行以下對比:
- (1) 請求兩個檔案+解密耗時,與本身 glb 檔案壓縮後的體積大小相比,真實性能對比;
- (2) 畫質是否會出現設計師無法接受的損失。
4.1.2KHR_mesh_quantization[4]
頂點屬性通常使用FLOAT類型存儲,将原始始浮點值轉換為16位或8位存儲以适應統一的3D或2D網格,也就是我們所說的quantization向量化,該插件主要就是将其向量化。
例如,靜态 PBR-ready 網格通常需要每個頂點POSITION(12 位元組)、TEXCOORD(8 位元組)、NORMAL(12 位元組)和TANGENT(16 位元組),總共 48 位元組。通過此擴充,可以用于SHORT存儲位置和紋理坐标資料(分别為 8 和 4 位元組)以及BYTE存儲法線和切線資料(各 4 位元組),每個頂點總共 20 位元組。
- 壓縮方式
可以使用gltfpack工具進行壓縮
gltfpack -i male.glb -o male-processed.glb
- 使用方式(在 Three.js 中)
普普通通地用就好了,和不壓縮的沒什麼差別
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
const loader = new GLTFLoader()
loader.load(MODEL_FILE_PATH, (gltf) => {
// ....
})
- 性能對比
原檔案3.2M,壓縮後1.9M,為原檔案的59.3%,比原模型加載速度也快上不少。放到實際項目中,沒有畫質損失和加載時間過長的問題。
4.1.3EXT_meshopt_compression[5]
此插件假定緩沖區視圖資料針對 GPU 效率進行了優化——使用量化并使用最佳資料順序進行 GPU 渲染——并在 bufferView 資料之上提供一個壓縮層。每個 bufferView 都是獨立壓縮的,這允許加載器最大程度地将資料直接解壓縮到 GPU 存儲中。
除了優化壓縮率之外,壓縮格式還具有兩個特性——非常快速的解碼(使用 WebAssembly SIMD,解碼器在現代桌面硬體上以約 1 GB/秒的速度運作),以及與通用壓縮相容的位元組存儲。也就是說,不是盡可能地減少編碼大小,而是以通用壓縮器可以進一步壓縮它的方式建構比特流。
- 壓縮方式
可以使用gltfpack工具進行壓縮
gltfpack -i male.glb -o male-processed.glb -cc
- 使用方式(在 Three.js 中)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'
const loader = new GLTFLoader()
loader.setMeshoptDecoder(MeshoptDecoder)
loader.load(MODEL_FILE_PATH, (gltf) => {
// ....
})
- 性能分析對比
原檔案3.2M,壓縮後1.1M,為原檔案的65.6%,首次加載時間比原模型快上不少。放到實際項目中,沒有畫質損失和加載時間過長的問題。
五、多個機型裝置與優化對比結果
為了避免上文提到的“draco”壓縮使得模型受損的情況,找了幾台iPhone、安卓的手機來進行了一下性能與相容的測試,讓我們看一下結果。PS:公司網絡在不同時間段内網速不同(如上午和下午),可能會對數字産生小部分影響,但不影響檔案優化橫向對比。
iPhone 12(iOS 14.4,自用)—
Huawei Mate 40 pro (HarmonyOS,自用)—
Xiaomi Mix2(Android 8.0,測試機)—
iPhone 6sp (iOS 13.7,自用機)—
5.1 總結—
可見,對于小部分需要使用模型的,并且隻需要加載一個模型的業務,采用KHR_mesh_quantization或EXT_meshopt_compression進行網格壓縮,再使用gltf-pipeline進行子產品區分并對紋理圖檔壓縮,是目前找到的較好的優化方案。
六、其他
其實還有很多性能優化的插件,目前正在進行調試和調查,等後續疊代或有什麼新進展,會繼續更新:
網格優化的:
- EXT_mesh_gpu_instancing[6]現 Three.js 的 GLTFLoader[7] 尚未支援,Babylon.js 的BABYLON.GLTF2.Loader.Extensions[8] 支援
還有一些紋理優化的插件:
- KHR_texture_basisu[9]
- EXT_texture_webp[10]
七、參考資料
[1]
glTF: https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_002_BasicGltfStructure.md
[2]
.glb 檔案: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
[3]
KHR_draco_mesh_compression: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md
[4]
KHR_mesh_quantization: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_mesh_quantization/README.md
[5]
EXT_meshopt_compression: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/EXT_meshopt_compression/README.md
[6]
EXT_mesh_gpu_instancing: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md
[7]
GLTFLoader: https://github.com/mrdoob/three.js/issues/21937
[8]
BABYLON.GLTF2.Loader.Extensions: https://doc.babylonjs.com/typedoc/classes/babylon.gltf2.loader.extensions.khr_mesh_quantization
[9]
KHR_texture_basisu: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu/README.md
[10]
EXT_texture_webp: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/EXT_texture_webp/README.md
[11]
The Basic Structure of glTF: https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_002_BasicGltfStructure.md
[12]
GLB File Format Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
[13]
Extensions for glTF 2.0: https://github.com/KhronosGroup/glTF/tree/master/extensions/
[14]
KHR_draco_mesh_compression: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md
[15]
DRACOLoader – three.js docs: https://threejs.org/docs/#examples/en/loaders/DRACOLoader
[16]
CesiumGS/gltf-pipeline: Content pipeline tools for optimizing glTF assets.: https://github.com/CesiumGS/gltf-pipeline
[17]
KHR_mesh_quantization: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_mesh_quantization/README.md
[18]
gltfpack | meshoptimizer: https://meshoptimizer.org/gltf/
[19]
GLTFLoader: https://threejs.org/docs/?q=GLTFLoader#examples/en/loaders/GLTFLoader
[20]
EXT_meshopt_compression: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/EXT_meshopt_compression/README.md
[21]
【網格壓縮測評】MeshQuan、MeshOpt、Draco: https://juejin.cn/post/6931954784018628621
作者:凹凸嫚-賽米老馮
來源:微信公衆号:凹凸實驗室
出處:https://mp.weixin.qq.com/s?__biz=MzIxMzExMjYwOQ==&mid=2651897079&idx=1&sn=5f3227367175adc666381526549fc0e5&chksm=8c5fdeb9bb2857afe45798de1032123f10a616829f967593a2460cb71320c0e00a38df8812ab&scene=21#wechat_redirect