基于 Three.js 的 3D 模型加载优化

作者:来自 vivo 互联网前端团队- Su Ning

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

一、前言

近段时间,我们使用three.js完成了vivo拟我形象的开发工作,大家可以在vivo账号中拟制属于自己的3D形象,也可以保存作为自己的头像名片。

图1.gif

作为一个3D的项目,从用户打开页面到最终模型的渲染需要经过多个流程,加载的时间也会比普通的H5项目要更长一些。然而过长的等待时间会造成大量的用户流失,这部分用户没有体验到具体的功能就退出了页面非常的遗憾,为了提升首屏加载的转化率,需要尽可能的降低loading的时间。这里就分享一些我们在模型加载优化方面的心得。

二、模型加载的优化思路

想对加载进行优化,首先需要了解Three.js加载模型时的工作流程,并分析出其中耗时的部分进行针对性的处理。

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

图2.png

三、缩小模型的体积

3.1 常见的解决方案

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

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

  • 可能在网格连接处存在画面模型撕裂。

  • 仅仅压缩顶点只能将50.7mb的人物模型压缩到49.5,压缩效果有限。

  • draco前台decoder在h5中的解算效率不理想,可能节省下来的网络请求时间还没有增加的数据解算的时间长。

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

图3.png

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

3.2 进阶方案

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

图4.png
图5.png

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

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

图6.png

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

图7.png

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

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

图8.png
图9.png

四、文件的加解密

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

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

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

图10.png

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

图11.png

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

优化完模型的加载,继续来优化模型的渲染,在加载一个体积比较大的模型的时候经常会有页面的卡死的情况出现,需要从两方面治标也治本的进行优化:

  1. 通过减少页面的卡停来优化用户的感官体验。

  2. 通过缩短首屏渲染的时长来解决根本问题。

5.1 减少页面的卡停

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

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

图12.png

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

5.2 缩短首帧渲染的时间

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

图13.png

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

  • /three/src/renderers/webgl/WebGLMorphtargets.js
图14.png

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

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

图15.png
图16.png

六、总结与规划

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

图17.png

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容