如何优化基于Three.js的web3D项目,减少模型渲染时间避免页面卡顿 ...

打印 上一主题 下一主题

主题 887|帖子 887|积分 2661

一、前言

作为一个3D的项目,从用户打开页面到最终模型的渲染必要经过多个流程,加载的时间也会比普通的H5项目要更长一些,从而造成大量的用户流失。为了提升首屏加载的转化率,必要尽大概的降低loading的时间。这里就分享一些我们在模型加载优化方面的心得。
二、模型加载的优化思路


想对加载进行优化,起首必要了解Three.js加载模型时的工作流程,并分析出此中耗时的部分进行针对性的处置惩罚。

在Three.js中,模型从加载到渲染必要经过模型下载、序列化模型、网格解析、写入缓存和渲染模型几个步骤,经过分析发现主要的瓶颈在网络请求和网格解析两个部分,所以团体的优化思路就是减少网络请求资源的体积和提升网格的解析速率。



三、缩小模型的体积


3.1 常见的解决方案

目前主流的压缩方案是使用google的draco库对模型进行压缩。draco的原理雷同降低图片的分辨率,通过减少模型的顶点数起到压缩体积的效果。

也就是说draco是一种有损的压缩方式,这样就会带来诸多的问题


  • 大概在网格毗连处存在画面模型撕裂。
  • 仅仅压缩顶点只能将50.7mb的人物模型压缩到49.5,压缩效果有限。
  • draco前台decoder在h5中的解算效率不理想,大概节省下来的网络请求时间还没有增长的数据解算的时间长。

基于以上几点,最终我们放弃了draco的压缩方案。



使用draco压缩之后导致的模型撕裂

3.2 进阶方案

高端的食材,往往只必要接纳最质朴的烹饪方式。经过一些实验,我们发现将glb模型直接打成zip包可以明显的提升模型的压缩效率。50.7mb的人物模型可以压缩到11.6mb。




但是Three.js提供的gltfloader是不能直接加载zip文件的,于是我们必要对其进行功能扩展。

Three.js加载gltf模型是起首通过fetch请求获取到模型的arraybuffer,再对arraybuffer进行格式化。所以我们只必要在模型格式化之前拦截zip文件进行解压缩即可。



于是我们使用jszip,资源加载完成后判断资源的后缀,假如是zip文件就使用jszip进行解压缩。



看起来还不错,在包管视觉效果的同时又可以大幅压缩模型的体积,那么有没有大概做的再极致一些呢?

既然是针对性的场景,我们就可以从解压缩的解算开始入手,于是我们使用rust写了一个解压工具,将其转换成wasm包代替jszip,可以发现wasm的冷启动性能确实要比js好许多,可以将解压的时长从几十到100毫秒降低到1毫秒左右,适合体积比力小的解压缩场景。






四、文件的加解密


作为一个h5项目,获取到静态资源的链接并不困难,所以必要对模型文件进行一点点加密,让破解起来没有那么容易。同时解密的过程不能显著延伸资源加载的时间,影响用户体验。

基于数据解密的效率,我们可以截取文件buffer的一部分进行加密,而不对全文进行加密,同时将数据解密的过程也放到wasm中,提升解算效率的同时也加强了安全性。接纳对称加密的算法,同一个方法既可以用于加密,也可以用于解密。

按照模型加载的流程,解密的操作应该放在解压缩之后,序列化之前,那么如何判断数据是否进行了加密呢,可以通过判断解压数据decode以后是否有glTF的标记来确定。



如下图,数据解密的耗时几乎可以忽略不计,可以放心使用。




五、如何优化首帧的渲染体验


优化完模型的加载,继承来优化模型的渲染,在加载一个体积比力大的模型的时间经常会有页面的卡死的环境出现,必要从两方面治标也治本的进行优化:
      1、通过减少页面的卡停来优化用户的感官体验。
      2、通过缩短首屏渲染的时长来解决根本问题。

5.1 减少页面的卡停

在模型加载的时间通常会设置一个loading页面来展示当前的加载进度,同时loading页也可以播放一些动效或者互动来让用户期待的过程中不那么无聊。但是由于js单线程的特性,在进行首帧渲染的时间任何事件都不无法相应,会让用户误以为页面卡死,造成流失。

为了解决这个问题我们可以使用分步加载的方案,在模型加载的时间先遍历第一层网格,将所有的网格隐蔽起来,然后循环这些网格,每展示一个就执行一次render方法,这样就可以把一个大的卡顿分散成多个小的,不至于影响前台的体验。



但是这样的方法只能让用户感受起来没那么卡顿,该等的时间一点没少,过长的期待时间还是会让用户等的不耐烦,有没有其他解决卡顿的方式呢?这就要从Three.js的渲染逻辑来进行分析了。

5.2 缩短首帧渲染的时间

由于我们做的是一个捏脸的项目,通过形态键来实现差异的脸型,心情等表现。在Three.js中存储形态键信息的属性在geometry.morphAttributes中,形态键存放的顶点信息总数与网格的顶点数相同,这就意味着同一个模型有多少个形态键,就额外必要加载多少套网格的顶点信息。在初次渲染的时间Three.js会遍历每一个形态键的顶点信息,生成一个float32array,而这个巨量的遍历操作就是造成卡顿的根本原因。



如何解决这个循环黑洞呢,我想到了steamdeck上的着色器预缓存,通过将着色器编译的效果进行长期化,缩短页面加载的时间。那么我们只要将每一个网格的形态键编译的效果储存起来就行了。

/three/src/renderers/webgl/WebGLMorphtargets.js



通过这种方式成功的将首帧渲染的时间从7秒缩短到0.6秒,大幅的提升了用户的体验。

讲到这里,大家大概发现了,虽然首帧渲染的时长缩短了,但是形态键缓存的资源有80mb,压缩后也有15mb,这块的时长可不可以继承压榨呢,先看一下资源的处置惩罚流程,处明白压后的文件必要将文件解析成JSON字符串,然后在转换成float32array,这里耗时最大的点就是JSON.parse的操作,有没有更好的方式处置惩罚呢,可以将这部分内容丢到rust内里,平均可以减少0.5s的时间。




六、总结与规划


以上就是我们的优化流程,将glb模型文件压缩成zip包,配合前台wasm解压工具降低模型的加载时间。通过增长形态键缓存的方式来降低首帧渲染的时长。



经过这一系列的操作,成功的将模型的体积从50mb压缩到11mb,增长了额外80mb的形态键缓存也可以使用zip压缩到15mb,处置惩罚后页面的初次加载时长从15秒缩短到5秒,算是一个不小的提升。

然而,我们也意识到还有进一步的优化空间,譬如目前虽然有了形态键缓存,但是原模型中的形态键信息还存储在模型中,这一部分的信息不必要被threejs读取,却很大的占用了模型的体积,后续可以开发一个gltf-pipeline雷同的处置惩罚工具,将形态键缓存直接整合进gltf模型中,同时把整个模型的序列化工作放到wasm中处置惩罚,降低模型的尺寸的同时也可以减少模型解析的时长。期待为大家带来更好的使用体验。




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表