乐趣区

关于前端:3D性能优化-说一说glTF文件压缩

引言

最近做 T 级互动,须要应用到 3D 模型。置信大家和我一样,在开始着手的时候,肯定会有这么些问题:

  • 1. 如何抉择 3D 模型的导出格局
  • 2. 如何对模型文件进行优化
  • 3. 在大流量的我的项目中兼容性怎么样

让咱们通过这篇文章,进行粗疏的摸索、调研与积淀。

一、什么是 glTF 文件

glTF 全称 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.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_webglKHR_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.1 KHR_draco_mesh_compression

常见 的一种网格压缩形式,采纳开源的 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.2 KHR_mesh_quantization

顶点属性通常应用 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.3 EXT_meshopt_compression

此插件假设缓冲区视图数据针对 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_quantizationEXT_meshopt_compression进行网格压缩,再应用 gltf-pipeline 进行模块辨别并对纹理图片压缩,是目前找到的较好的优化计划。

六、其余

其实还有很多性能优化的插件,目前正在进行调试和考察,等后续迭代或有什么新进展,会持续更新:

网格优化的:

  • EXT_mesh_gpu_instancing

    现 Three.js 的 GLTFLoader 尚未反对,Babylon.js 的 BABYLON.GLTF2.Loader.Extensions 反对

还有一些纹理优化的插件:

  • KHR_texture_basisu
  • EXT_texture_webp

七、参考资料

  1. The Basic Structure of glTF
  2. GLB File Format Specification
  3. Extensions for glTF 2.0
  4. KHR_draco_mesh_compression
  5. DRACOLoader – three.js docs
  6. CesiumGS/gltf-pipeline: Content pipeline tools for optimizing glTF assets.
  7. KHR_mesh_quantization
  8. 📦 gltfpack | meshoptimizer
  9. GLTFLoader
  10. EXT_meshopt_compression
  11. 【网格压缩测评】MeshQuan、MeshOpt、Draco
退出移动版