概述
随着智慧城市建设应用中的模型面积与渲染数据不断增大,性能就成了大场景应用的主要瓶颈。那么在大场景应用中我们有什么优化的方法呢,现在就让我们来聊聊,如何发现那些阻碍应用流畅运行的元凶,以及如何驯服 “它们”。
优化神器:DevTools
使用 chrome 的小伙伴们,应该都使用过这个神器,而其中的 Performance 更是探索性能瓶颈的关键工具。从中,我们可以清晰的看到 JavaScript 每个任务所消耗的时间,应用运行时每个时间点的 CPU,内存,GPU 的压力以及当前时间点的 FPS。由此,我们可以很容易的发现性能瓶颈出现的地方。
优化方向
从大体上来分,优化方向大致可以分为以下几类:
1、代码优化
2、内存优化
3、渲染优化
4、加载优化
我们会根据每个大的方向,讲讲如何具体的采取哪些策略进行优化。首先我们需要知道性能瓶颈的由来。这时候,使 chrome 的开发者工具中的 performance 模块进行性能诊断是我们的重要手段。
代码优化
一般情况,代码层面的优化可以通过 Performance 找到执行效率低的代码片段,对算法进行优化,或者修改不合理的相关逻辑。在此列举一些常用的 JavaScript 的优化方法。
1、使用节流和防抖
2、使用异步编程,防止线程阻塞
3、使用 Web Workers 在后台运行 CPU 密集型任务
4、减少重绘操作
5、使用位运算
6、http 请求的合并和压缩(GZIP)
这部分受项目和业务的影响而不同,在此不做深入的探讨。
内存优化
在大型 WebGL 应用中,特别是存在 GIS、BIM 的场景,内存资源往往是非常吃紧。在这种情况下,首先我们要即时销毁不需要的对象,并释放内存,尤其是模型数据。模型数据中的顶点 buffer,法线 buffer 数据量是非常大的,这些数据在推送给 GPU 后,可以立即将其释放,以减少内存的压力,还可以避免 GC 的多次执行所造成应用的卡顿。在浏览器中,JavaScript 的 heap 容量是有限的,一旦超出容量的限制,页面就会直接崩溃,这对应用来说是致命的问题。
此外,从 GPU 的角度出发,如果 GPU 显存使用完了,会开始使用系统的 GPU 共享内存。GPU 共享内存是系统划分出来一块优先供给 GPU 使用的内存。因此,珍惜 GPU 的显存空间也同样重要,其方法主要是,不重复向 GPU 添加相同的材质,及时删除不使用的模型和图绘。
渲染优化
渲染优化的目的是提高每秒渲染的帧数,主要的两种途径是减少 Draw Call(调用图形编程接口)和减少向 GPU 提交的数据量。
在每次调用 Draw Call 之前,CPU 需要向 GPU 发送很多内容,包括数据、状态、命令等。在这一阶段,CPU 需要完成很多工作,例如检查渲染状态等。而一旦 CPU 完成了这些准备工作,GPU 就可以开始本次的渲染。GPU 的渲染能力是很强的,渲染 300 个和 3000 个三角网格通常没有什么区别,因此渲染速度往往快于 CPU 提交命令的速度。如果 Draw Call 的数量太多,CPU 就会把大量时间花费在提交 Draw Call 命令上,造成 CPU 的过载。我们可以通过合并多个小 Draw Call 为一次大的 Draw Call 来实现。
减少向 GPU 提交数据量则是通过剔除不需要渲染的部分数据来实现,一般常见的剔除方式有视椎体剔除,背面剔除、遮挡剔除等。
视椎体剔除(Frustum Culling):一般是指只有在视椎体内的物体才能被渲染出来,不在视椎体内的物体将被剔除不作渲染。这也比较符合我们一般的视觉逻辑,不在可见范围内的物体,渲染了也看不见,纯粹属于性能浪费。这部分的剔除,大部分的引擎都已经自带了,而且都是默认开启的。如果需要自己实现,算法也比较简单,一般是遍历视椎体的 6 个面,算出物体的中心到面的最小距离(带正负方向的)与包围球的半径做比较,如果小于半径,就表示在外面。
■ 背面渲染剔除 (Backface Culling) :
一般来讲,渲染引擎大多会开启背面剔除。原生 WebGL 中使用 gl.enable(gl.CULL_FACE); 来开启背面剔除。
■ 遮挡剔除 (Occlusion Culling) :
遮挡剔除是指在相机剔除后,在视野范围内仍然有许多物体直接有遮挡关系的,不需要进行渲染,虽然 gpu 有深度测试,会将有遮挡的物体进行剔除,但是我们仍然希望在提交 GPU 之前对遮挡关系进行判断,提前剔除掉一些东西,减少渲染压力。
在该项目中,通过上述三种剔除策略,极大地减少了 GPU 渲染所需加载的数据量,并且在一次调用图形编程接口中整合了多个细碎渲染任务,最终实现了快速流畅加载大场景的需求。
加载优化
在大场景应用中,加载资源的速度是用户对应用的第一印象,这直接决定用户对应用直观评价。因此,如何更加快速的加载所需的资源,渲染出完整的场景是我们优化的重中之重。
大场景意味着大量数据,其加载会受到带宽限制,从浏览器下载大量数据往往需要一定时间,从这个方面来讨论,我们可以通过服务端来做第一步优化。以 nginx 为例,我们可以开启其中的 gzip 功能,以实现数据的压缩传输。该传输在浏览器和服务器之间是默认完成的,不需要我们在浏览器中做处理。判断是否开启 gzip 的方法, 若出现下图所示的请求头,则表示已经开启。
对于大型 gltf 模型来说,我们可以通过 Draco 对模型进行压缩,Draco 通过减少顶点坐标、顶点纹理坐标等信息的位数,以减少数据的存储量。但该方法具有两面性,在减少了数据量的同时,压缩也不可避免的对模型造成一定程度的损伤。同时,Draco 在浏览器中解压缩对 CPU 资源的消耗较大,解压缩也需要占用一定的时间,阻塞模型渲染。在该项目中,我们就通过使用 Draco 并且调整 Draco 的压缩策略,实现了管线模型加载的优化,提升了数倍的加载效率。
对于海量点的加载情况,我们可以使用 Primitive 代替 Entity,在绘制大量 Primitive 时,可以将其合并为单个 Geometry,减轻 CPU 负担,更好使用 GPU。除此之外,一个 Geometry 中包含过多 Primitive 也会对性能产生副作用,并且一次加载过多的 Primitive 会导致应用阻塞。我们可以通过 JavaScript 中的计时器使 Primitive 分批次渲染,这样既解决了 Geometry 过大的问题,也防止渲染被阻塞。
在某项目中,该场景需要渲染 6000 + 个面,使用 entity 时,不仅渲染速度极慢,还因为渲染的任务导致整个线程被阻塞。最终通过该优化方案,使该场景在预期的时间内完成渲染,并且渲染过程不影响项目的运行。
本文来自“宝略科技”
宝略科技秉承 “用科技打开新世界” 的核心价值观,为智慧城市建设提供最先进的综合解决方案,包括城市工业级无人机遥感平台服务、室内外一体化高精度地理数据服务、云 GIS 应用系统开发、室内数字化与导航服务、BIM 解决方案。
https://mp.weixin.qq.com/s/NvKKPajtu8EuY7K6oCohNA