最近做了一个元宇宙概念的3D网页应用,用户可以在虚拟空间里漫游、和其他用户互动。初始版本上线后,帧率只有20fps,在低端设备上甚至只有10fps,体验很差。经过一周的优化,最终稳定在60fps,低端设备也能达到30fps以上。本文分享完整的性能优化过程。
一、项目背景
这个应用是基于Three.js开发的Web端3D虚拟空间,包含一个大型场景、多个可交互的3D物体、实时多人同步和语音聊天功能。用户可以用键盘控制角色移动,鼠标控制视角,和场景中的物体交互。
初始版本的问题很明显:场景加载慢(超过10秒)、帧率低(平均20fps)、发热严重(笔记本风扇狂转)、低端设备无法运行。用户反馈很不好,必须优化。
我用Chrome DevTools的Performance面板和Three.js的renderer.info做了性能分析,找到了几个主要瓶颈:模型面数太高、draw call太多、纹理太大、没有做视锥剔除和LOD。
二、模型优化
模型是最大的性能瓶颈,我们从几个方面做了优化。
1. 减面。 初始场景的总面数超过200万面,这是帧率低的主要原因。我们用Blender对所有模型做了减面处理,在不影响视觉效果的前提下,把总面数降到了50万面。关键是远处的物体可以大幅减面,近处的物体保留细节。
2. 合并模型。 场景中有很多重复的小物体,比如椅子、桌子、植物,每一个都是单独的mesh,导致draw call很高。我们把相同材质的物体合并成一个mesh,draw call从300多降到了50多,帧率直接提升了一倍。
3. 使用glTF格式。 初始版本用的是OBJ格式,文件大、加载慢。换成glTF格式后,文件体积减小了60%,而且支持Draco压缩,加载速度提升明显。
4. LOD(细节层次)。 对远处的物体使用低精度模型,近处使用高精度模型。Three.js有内置的LOD支持,实现起来很简单。加了LOD之后,远处的物体面数大幅减少,帧率又提升了不少。
三、渲染优化
模型优化之后,渲染层面还有优化空间。
1. 视锥剔除。 Three.js默认会做视锥剔除,但我们的场景中有一些大物体没有正确设置bounding box,导致剔除失效。手动修正了所有模型的bounding box之后,不在视野内的物体不会被渲染,性能提升明显。
2. 遮挡剔除。 对于被其他物体完全遮挡的物体,也不需要渲染。我们用了一个简单的遮挡剔除方案,对大型建筑物内部的物体做了特殊处理,只有当用户进入建筑物时才渲染内部细节。
3. 纹理优化。 初始版本的纹理都是2048x2048的,有些甚至是4096x4096,显存占用很大。我们根据物体的重要性和大小,把纹理降到了合适的尺寸,大部分物体用1024x1024就够了,小物体用512x512。同时开启了纹理压缩,显存占用减少了一半。
4. 阴影优化。 阴影是性能杀手。初始版本所有物体都投射和接收阴影,阴影贴图分辨率是2048x2048。我们把阴影贴图降到1024x1024,只让重要的物体投射阴影,远处的物体不投射阴影。阴影的质量下降不明显,但性能提升很大。
5. 后处理优化。 初始版本加了很多后处理效果:抗锯齿、泛光、色调映射、景深。这些效果每一个都要渲染一遍场景,性能开销很大。我们保留了抗锯齿和泛光,去掉了景深,色调映射改成了更轻量的算法。如果用户设备性能差,还可以自动关闭泛光。
四、代码优化
除了模型和渲染,代码层面也有优化空间。
1. 减少每帧的计算。 初始版本在每帧的update函数里做了很多计算,比如距离检测、碰撞检测、UI更新。我们把不需要每帧计算的逻辑改成了定时执行,比如每100ms检测一次距离,每500ms更新一次UI。这样每帧的计算量减少了很多。
2. 对象池。 场景中有一些频繁创建和销毁的对象,比如粒子、特效。我们用对象池来复用这些对象,避免频繁的内存分配和垃圾回收。加了对象池之后,帧率更稳定了,不会出现周期性的卡顿。
3. 按需加载。 初始版本把整个场景的所有资源都加载完才让用户进入,加载时间很长。我们改成了按需加载:先加载用户出生点附近的资源,用户进入后再逐步加载远处的资源。初始加载时间从10秒降到了3秒,用户体验好了很多。
4. WebWorker。 把一些耗时的计算(比如路径规划、数据解析)放到WebWorker里执行,避免阻塞主线程。这样即使计算量很大,也不会影响渲染帧率。
五、多人同步优化
这个应用有多人实时同步功能,这部分也有性能问题。
1. 减少同步频率。 初始版本每帧都同步所有用户的位置和状态,网络带宽和CPU开销都很大。我们改成了每100ms同步一次,客户端做插值平滑,用户感觉不到延迟,但网络流量减少了80%。
2. 只同步变化的数据。 初始版本每次同步都发送完整的状态数据,包括没有变化的字段。我们改成了只发送变化的字段,数据包大小减小了很多。
3. 范围同步。 只同步用户附近的其他用户,远处的用户不同步。这样用户数量多的时候,性能也不会下降太多。
六、优化效果
经过上述优化,性能提升非常明显:
- 帧率:从平均20fps提升到稳定60fps
- 加载时间:从10秒降到3秒
- 显存占用:从1.5GB降到600MB
- 低端设备:从无法运行到30fps以上
- 发热:笔记本风扇不再狂转
用户体验好了很多,留存率也提升了。
七、优化过程中的教训
这次性能优化让我收获了几个教训:
- 先测量再优化。 不要凭感觉优化,先用工具找到真正的瓶颈,再针对性地优化。我们一开始以为是代码的问题,后来发现主要瓶颈在模型和渲染。
- 视觉效果和性能要平衡。 不是所有效果都要保留,有些效果对体验影响不大但性能开销很大,该砍就砍。
- 从一开始就考虑性能。 如果在开发初期就注意性能,后期优化会轻松很多。我们就是初期太粗放,后期花了大量时间补课。
- 分级策略很重要。 不同性能的设备用不同的画质设置,高端设备开满效果,低端设备降低画质,保证所有用户都能流畅运行。
- 持续监控。 优化不是一次性的,新功能上线可能会引入新的性能问题,需要持续监控和优化。
八、写在最后
3D网页应用的性能优化是一个系统工程,涉及模型、渲染、代码、网络等多个层面。没有银弹,需要根据具体情况找到瓶颈,然后针对性地优化。
这次优化从20fps到60fps,看起来提升很大,但其实每一项优化的提升都不算多,加在一起效果就很明显了。性能优化就是这样,一点一点地挤,挤到最后就能达到目标。
如果你也在做3D网页应用,希望我的经验能帮到你。记住:先测量,再优化;平衡效果和性能;从一开始就考虑性能。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录