2021-凹凸嫚-3D性能优化glTF文件压缩

3D性能优化glTF文件压缩

一、什么是glTF文件

glTF全称Graphics Language Transmission Format,是三维场景和模型的标准文件格式。glTF核心是JSON文件,描述了3D场景的整个内容。它由场景结构本身的描述组成,其由定义场景图的节点的层次提供。

场景中出现的3D对象是使用连接到节点的meshes(网格)定义的。Materials(材料)定义对象的外观。Animations(动画)描述3D对象如何随着时间的推移转换3D对象,并且Skins(蒙皮)定义了对物体的几何形状的方式基于骨架姿势变形。Cameras(相机)描述了渲染器的视图配置。

除此以外,它还包括了带有二进制数据和图像文件的链接,如下图所示。

glTF JSON 文件格式

二、.gltf.glb

blender文件导出中可以看出:

  • glTF Binary(.glb)
  • glTF Embedded(.gltf)
  • glTF Separate(.gltf + .bin + textures)

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文件预览

  • 自包含的:

GLB 自包含

  • 引用外部二进制和纹理资源:

引用二进制与纹理资源

从图中可以看到,当非自包含型的时候,请求glTF文件时,会一同请求图片文件。

三、glTF文件拆分

上文提到,glTF文件可以拆分为.gltf/.glb文件+二进制文件+纹理图片,那么,我们就可以将其拆分出来,并对纹理图片进行单独的压缩,来进行性能的优化。

可以使用gltf pipeLine ,其具有以下功能:

  • glTFglb的相互转换
  • 将缓冲区/纹理保存为嵌入或单独的文件
  • 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相关的插件主要有以下:

glTF 2.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.2Mdraco压缩后为1.8M,约为原文件的56%。从上面的代码中可以看出,创建解码器实例需要引入额外的库来进行解码,setDecoderPath会自动请求wasm文件来进行解密操作。而这两个wasm文件同时也增加了请求时间和请求数量,那么加上这两个文件,真实的压缩率约为62.5%

请求对比

所以,如果一个项目需要加载多个glTF文件,那么可以创建一个DRACOLoader实例并重复使用它。但如果项目只需要加载一个glTF文件,那么使用draco算法是否具有“性价比”就值得考量了。

性能对比

可见draco算法首次加载和解密时间,要大于原文件。而在实际项目中,这个差距更加明显,并且偶尔会出现解密堵塞的情况,需要重新进入页面才能恢复功能。

除此以外,还有一个很直观的问题,模型画质的损失是肉眼可观的。如图,分别是在iPhone 12和小米MIX2中的样子:

模型图片

总而言之,如果要将draco压缩算法运用到大规模项目中,需要结合实际项目进行以下对比:

(1)请求两个文件+解密耗时,与本身glb文件压缩后的体积大小相比,真实性能对比; (2)画质是否会出现设计师无法接受的损失。

4.1.2 KHR_mesh_quantization

顶点属性通常使用FLOAT类型存储,将原始始浮点值转换为16位或8位存储以适应统一的3D2D网格,也就是我们所说的quantization向量化,该插件主要就是将其向量化。

例如,静态PBR-ready网格通常需要每个顶点POSITION12字节、TEXCOORD(8字节、NORMAL(12字节)和TANGENT16字节,总共48字节。通过此扩展,可以用于SHORT存储位置和纹理坐标数据(分别为84字节)以及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%,首次加载时间比原模型快上不少。放到实际项目中,没有画质损失和加载时间过长的问题。

性能对比