引言

最近做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进行模块区分并对纹理图片压缩,是目前找到的较好的优化方案。

六、其他

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

网格优化的:

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

七、参考资料

  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


欢迎关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:

3D性能优化 | 说一说glTF文件压缩的更多相关文章

  1. 前端性能优化成神之路-HTTP压缩开启gzip

    什么是HTTP压缩 HTTP压缩是指: Web服务器和浏览器之间压缩传输的”文本内容“的方法. HTTP采用通用的压缩算法,比如gzip来压缩HTML,Javascript, CSS文件. 能大大减少 ...

  2. [Unity 3D] Unity 3D 性能优化 (一)

    听到过很多用Unity 3D开发游戏的程序员抱怨引擎效率太低,资源占用太高,包括我自己在以往项目的开发中也头疼过.最近终于有了空闲,可以仔细的研究一下该如何优化Unity 3D下的游戏性能.其实国外有 ...

  3. [Unity 3D] Unity 3D 性能优化(二)

    IsAlive U3D的粒子系统脚本接口相信很多人都用过,ParticleSyetem类的一系列接口都有一个bool类型的参数——withChildren,通过这个参数可以直接将相同的判断或者操作应用 ...

  4. 【Java技术专题】「性能优化系列」针对Java对象压缩及序列化技术的探索之路

    序列化和反序列化 序列化就是指把对象转换为字节码: 对象传递和保存时,保证对象的完整性和可传递性.把对象转换为有字节码,以便在网络上传输或保存在本地文件中: 反序列化就是指把字节码恢复为对象: 根据字 ...

  5. Nginx 性能优化

    1.安全优化:隐藏Nginx版本号,server_tokens off; 2.安全优化:更改掉默认的用户  user nginx; 3.性能优化:  根据硬件配置,调整nginx worker 进程数 ...

  6. 前端性能优化成神之路—资源合并与压缩减少HTTP请求

    资源合并与压缩减少HTTP请求的概要 资源合并与压缩减少HTTP请求主要的两个优化点是减少HTTP请求的数量和减少请求资源的大小 http协议是无状态的应用层协议,意味着每次http请求都需要建立通信 ...

  7. 常见 Web 性能优化方式

    这篇文章是我阅读 Web Performance 101 之后的进行的粗糙的翻译作为笔记,英语还行的童鞋可以直接看原文. 这篇文章主要介绍了现代 web 加载性能(注意不涉及代码算法等),学习为什么加 ...

  8. (整理) .NET IIS性能优化

    本文收集了部分性能优化的方式,缓存.压缩.线程池调整等等,仅供参考. 1 .NET 程序中的调整 程序Sqlhelper中使用缓存 使用JSON序列化器(Jil)取代Json.NET 2 .NET 程 ...

  9. 01.SQLServer性能优化之----强大的文件组----分盘存储

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 文章内容皆自己的理解,如有不足之处欢迎指正~谢谢 前天有学弟问逆天:“逆天,有没有一种方 ...

随机推荐

  1. 11、文件比较与同步工具(FreeFileSync)

    11.1.基本介绍: 1.FreeFileSync是一个用于文件同步的免费开源程序.FreeFileSync通过比较其内容,日期或文件大小上的一个或多个文件夹,然 后根据用户定义的设置同步内容.除了支 ...

  2. __sync_fetch_and_add函数(Redis源码学习)

    __sync_fetch_and_add函数(Redis源码学习) 在学习redis-3.0源码中的sds文件时,看到里面有如下的C代码,之前从未接触过,所以为了全面学习redis源码,追根溯源,学习 ...

  3. 基于uni-app全端弹框组件uaPopup「兼容h5+小程序+app端|nvue」

    uniapp兼容多端自定义模态弹框组件UAPopup ua-popup 一款轻量级的uniapp自定义弹窗组件.汇集了android.ios和微信弹窗效果(msg消息.alert提示框.dialog对 ...

  4. Blazor 组件入门指南

    翻译自 Waqas Anwar 2021年3月19日的文章 <A Beginner's Guide to Blazor Components> [1] Blazor 应用程序是组件的组合, ...

  5. MySql:mysql修改密码及配置远程连接

    通过配置文件修改 mysql5.7  mysql库下面的user表没有password字段无法修改密码,5.7版本已经不再使用password来作为密码的字段了  而改成了authentication ...

  6. spring boot 集成mqtt

    1.pom文件中添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifact ...

  7. css 层叠上下文和层叠顺序

    层叠上下文是css中的一个三维概念,拥有层叠上下文的元素在z轴上比普通元素要高,而且其内部的所有内容是自成体系的,其后代都是在它的层叠顺序中 哪些元素拥有层叠上下文 1.根元素,也就是html默认拥有 ...

  8. win10 IIS web.config加密不能访问:打不开 RSA 密钥容器

    C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys 找到密钥文件, 根据时间判断具体是哪一个文件,赋予network service读权限

  9. 未开通js之前的纯css网页主题

    本主题修改自其他大佬:Rocket1184/MaterialCnblogs: Material Theme for cnblogs.com (github.com) 只需在博客后台选择"禁用 ...

  10. springboot-6-springSecurity

    一.Spring Security的基本配置 安全需要在设计网站之初就需要做好设计 可以做到: 功能权限 访问权限 菜单权限 这些权限虽然用拦截器过滤器也能实现,但是很麻烦,所以我们一般使用框架实现 ...