Geometry增量更新

前言

  优化 DrawCall 是图形学性能优化中老生常谈的问题,而针对 DrawCall 优化有很多方案,大致可分为两种:简化(Simplification)和合并(Consolidation),简化是指减少三角形个数,即将精细的模型变为粗糙的模型以及各种三角形剔除方案(视锥体剔除(Frustum Culling),遮挡剔除(Occlusion Culling)等),而合并自然则是将同一中材质下的多个 geometry 合并成一个 geometry。

需求篇

  最近一段时间一直在做高精地图道路的编辑,道路的编辑涉及到很多东西,这篇仅简单谈谈编辑性能的问题。为了能更直白的显示高精地图,不能简单的只使用点和线,还需要使用面将路面和路面上的一些路面标识精确的还原出来。在可视化道路时,主要有两种方案:1、将每条道路作为一个单独的 Mesh,即单独控制每条道路的渲染,一条道路至少产生一次 DrawCall,这样可以更方便的对每条道路进行编辑,但在渲染时要求更高的性能,而且道路一多,将不可避免的引起卡顿;2、将所有道路作为一个 Mesh,即直接渲染出一整个路网,这样显著降低了 DrawCall 次数,使渲染更流畅,但问题在于每编辑一次道路时,都需要重新三角化(Tessellation)整个路网,而且在选中一条道路时,为可视化选中效果,同样需要重新三角化该道路,并生成相应的 Mesh,导致编辑卡顿,每编辑完一次都可能需要等待一会儿。所以为了平衡渲染性能和编辑效率,需要有一种折中的方案,即对整个路网的 Geometry 能做到快速的分离与合并,在编辑时将受影响的道路分离出来,而在编辑之后,又将全部道路合并一下,提高显示性能。下面就谈谈 Shaun 对这种方案的一些思考。

编辑篇

  Shaun 为平衡渲染性能和编辑效率,想出的一种方案是增量更新 Geometry,即只删除或增加局部的 Geometry,而其它不受影响的 Geometry 保持原样,如此即可达到快速的分离和合并 Geometry。具体做法如下:

  1. 首先将所有道路的三角化结果合并成一个 Geometry,在合并的同时建立好每条道路的顶点索引以及面的索引(js 中可直接使用 object 进行存储);

  2. 在选择时,根据顶点索引和面索引重建一个 Geometry,再基于该 Geometry 构建一个新的 Mesh 以指示选择效果;

  3. 当删除道路时,需要删除面索引对应的所有面,而顶点索引对应的顶点不需要删除,将顶点索引移到一个用来标识该部分顶点已废弃的容器 F 中;

  4. 当新增道路时,需要先从容器 F 中查找是否有合适的地方放置该道路的顶点,若有,则放置在对应地方,并更新容器 F 中对应元素,若没有,则将该道路的顶点放置在 Geometry 顶点数组的最末尾,放置完顶点之后,同样需要建立该道路的顶点索引和面索引。

※注:至于容器 F 使用怎样的数据结构以及其中的元素该怎样排列,针对不同的顶点索引可以有不同的选择;在新增道路时,同样可以不同的策略来决定放置顶点的位置(可参考操作系统内存分配的模式)。

  由于新增道路时,可能会在容器 F 中产生一些永远无法删除的元素,导致顶点数组空闲碎片。 为抵抗顶点碎片以及减少顶点数目,需要对顶点数据进行压缩(Compaction),即移除没有使用的顶点,将后面的顶点前移,在前移顶点的同时,别忘了需要同时修改面中相应顶点的索引以及更新构建好的每条道路的顶点索引。

后记

  Shaun 这里只是提出了一种想法,最终实现起来发现效果也确实能达到基本需求(针对有很多条道路的大地图,显示性能从原来的十几二十帧到现在的 60 帧,同时选择和编辑也没受到影响),虽然在内存上比原来多增加了近 20%(还有优化的余地),但是为了渲染流畅以及编辑舒服,在如今这个内存越来越不值钱的年代,这种牺牲 Shaun 觉得是能接受的。当然或许有更好的方案,但限于 Shaun 目前的认知,只能暂时想到这一方案了,若有大佬有更好的方案,还望不吝赐教 🙏。