WebGL大场景性能优化
概述
随着智慧城市建设应用中的模型面积与渲染数据不断增大,性能就成了大场景应用的主要瓶颈。那么在大场景应用中我们有什么优化的方法呢,现在就让我们来聊聊,如何发现那些阻碍应用流畅运行的元凶,以及如何驯服“它们”。
优化神器: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时,不仅渲染速度极慢,还因为渲染的任务导致整个线程被阻塞。最终通过该优化方案,使该场景在预期的时间内完成渲染,并且渲染过程不影响项目的运行。
近期精选
- END -
关于我们
在省委省政府数字化改革大旗下,通过多年的案例积累和研发投入,宝略科技围绕数字经济、数字政府、数字社会等,目前已经推出和正在实施包括古城保护、城市更新建设、产业地图、楼宇经济、基层治理、公安防控等多个行业优秀解决方案。未来,宝略科技将进一步以地理信息为视角,创新打造富有宝略特色的深应用、高科技的政府数字化解决方案,成为政府数字化治理的先行者与行业领导者。
”
编辑 | 潘永恩
排版 | 苏珍缘
一审 | 卞婷玉
终审 | 孙 华