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

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

一、前言

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

作为一个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中处理,降低模型的尺寸的同时也可以减少模型解析的时长。期待为大家带来更好的使用体验。

基于 Three.js 的 3D 模型加载优化的更多相关文章

  1. MD2关键桢动画3D模型加载.

    在看Cg教程中,看到关键桢插值来表示一个动画的物体,例如一个动物拥有站着,奔跑,下跪等动画序列,美工将这些特定的姿态称为一个关键桢.为什么要用关键桢这种来表示了,这个比较容易理解,我们知道我们看的一些 ...

  2. three.js 运行3D模型

    HTML  <!DOCTYPE html> <html style="height: 100%;"> <head> <title>m ...

  3. 基于C++的OpenGL 14 之模型加载

    1. 引言 本文基于C++语言,描述OpenGL的模型加载 前置知识可参考: 基于C++的OpenGL 13 之Mesh - 当时明月在曾照彩云归 - 博客园 (cnblogs.com) 笔者这里不过 ...

  4. WPF 3D动态加载模型文件

    原文:WPF 3D动态加载模型文件 这篇文章需要读者对WPF 3D有一个基本了解,至少看过官方的MSDN例子. 一般来说关于WPF使用3D的例子,都是下面的流程: 1.美工用3DMAX做好模型,生成一 ...

  5. three.js实现3D模型展示

    由于项目需要展示3d模型,所以对three做了点研究,分享出来 希望能帮到大家 先看看效果: three.js整体来说 不是很难 只要你静下心来研究研究 很快就会上手的 首先我们在页面上需要创建一个能 ...

  6. AngularJS进阶(三十九)基于项目实战解析ng启动加载过程

    基于项目实战解析ng启动加载过程 前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理.回过头来总结一下angular的启动过程. 下面以实际项目为例进行简要讲解. 1.载入ng库 2 ...

  7. DirectX11 With Windows SDK--19 模型加载:obj格式的读取及使用二进制文件提升读取效率

    前言 一个模型通常是由三个部分组成:网格.纹理.材质.在一开始的时候,我们是通过Geometry类来生成简单几何体的网格.但现在我们需要寻找合适的方式去表述一个复杂的网格,而且包含网格的文件类型多种多 ...

  8. OpenGL OBJ模型加载.

    在我们前面绘制一个屋,我们可以看到,需要每个立方体一个一个的自己来推并且还要处理位置信息.代码量大并且要时间.现在我们通过加载模型文件的方法来生成模型文件,比较流行的3D模型文件有OBJ,FBX,da ...

  9. MUI - 基于plus.downloader的图片懒加载功能,支持本地缓存

    基于plus.downloader的图片懒加载功能,支持本地缓存 简单说一下 在app中,对一些变动不频繁的图片数据(如个人头像等),是需要存储在本地的.我相信这对大多数的app都是强需求的. 怎么使 ...

  10. CC模型加载太慢?一招破解!

    伴随无人机性能的提升,单个项目涉及到的倾斜摄影数据范围不断扩大,模型的数据量越来越大,在同配置机器上的显示速度也相应的越来越慢,那么如何在不升级配置的情况下提升模型的加载速度呢? 01 百GB倾斜摄影 ...

随机推荐

  1. MySQL知识网络

    MySQL知识网络 引擎 InnoDB 支持表锁 .行锁 支持事务 *.frm 表结构文件 *.idb 表数据和索引文件 MyISAM 支持表锁 *.frm 表结构文件 *.MYD 表数据文件 *.M ...

  2. Flutter(一):MAC的Flutter安装指南

    官网地址 官网: https://flutter.dev Github: https://github.com/flutter/flutter Git的核心分支包括master.dev.stable. ...

  3. 一分钟部署prometheus&grafana全方面监控SpringBoot项目

    0x01 创建目录 找一个你喜欢的地方,创建项目根目录 example: [root@demo-78 ~]# mkdir /data/prometheus 0x02 创建配置文件 进入到项目根目录: ...

  4. Centos Stream 10 测试版下载:未来的RHEL10&Rocky Linux 10

    简介 最近发现Centos最放出了Stream 10 测试版本,应该是基于Fedora 40构建的.未来红帽会基于此版本构建RHEL 10. 内核版本:6.9.0 Python版本:3.12.2 RH ...

  5. vue3项目安装依赖报错 npm ERR! code ERESOLVE

    vue3项目安装依赖报错 npm ERR! code ERESOLVE npm ERR! ERESOLVE could not resolve npm ERR! npm ERR! While reso ...

  6. Kubernetes OOM 和 CPU Throttling 问题

    介绍 使用 Kubernetes 时,内存不足(OOM)错误和 CPU 限制(Throttling)是云应用程序中资源处理的主要难题.为什么呢? 云应用程序中的 CPU 和内存要求变得越来越重要,因为 ...

  7. LangChain让LLM带上记忆

    最近两年,我们见识了"百模大战",领略到了大型语言模型(LLM)的风采,但它们也存在一个显著的缺陷:没有记忆. 在对话中,无法记住上下文的 LLM 常常会让用户感到困扰.本文探讨如 ...

  8. Mysql主机环境导入导出数据

    mysql数据库,在主机环境下导出数据为csv文件. 命令:select * into outfile '/mysql/11.csv' from 表; 可能会报错:ERROR 1290 (HY000) ...

  9. 前台往后台传id

    js页面var stuid;$(function () { var request = { QueryString : function(val) { var uri = window.locatio ...

  10. 『手撕Vue-CLI』自动安装依赖

    开篇 经过『手撕Vue-CLI』拷贝模板,实现了自动下载并复制指定模板到目标目录.然而,虽然项目已复制,但其依赖并未自动安装,可能需要用户手动操作,这并不够智能. 正如前文所述,我们已经了解了业务需求 ...