图形引擎实战:一种大世界巨量植被管理与渲染方案

您所在的位置:网站首页 layuidate重新渲染 图形引擎实战:一种大世界巨量植被管理与渲染方案

图形引擎实战:一种大世界巨量植被管理与渲染方案

2023-03-28 05:04| 来源: 网络整理| 查看: 265

Unity目前在大世界开发中提供的相关解决方案甚少,大部分开发团队都需要根据自己项目需求重新定制一套大世界管理和渲染方案,在这之间,大世界中的巨量植被渲染是需要解决的要点之一。本文基于这一需求,结合笔者的项目经验,简要介绍一种比较简单的在大世界场景中管理巨量植被对象和绘制的方案,供大家参考。

1.核心思路

本文主要探讨大世界中的植被管理,一般来说,能称为“大世界”的场景,其面积基本都在十几公里*十几公里以上,在这种环境中,美术种植的植被数量甚至可能会达到上千万单位的程度。因此在介绍本方案的核心思路之前,我们先总结一下如果直接使用Unity 默认的GameObject方式管理植被会有哪些问题。

场景GameObject至少包括Transform、MeshFilter、MeshRenderer三个组件,序列化属性较多,通过这种方式序列化巨量的植被,对包体、运行内存的影响非常大;即使通过大世界流式加载的方式管理植被对象,在植被密度大的地方,也可能会有短时间内需要同时实例化或卸载大批量植被的情况,导致帧率不稳;在野外等植被密集的地方,同一时刻场景中可能存在数万单位植被,Unity对这么大数量的GameObject的管理、视锥裁剪、Lod计算会占用大量的cpu时间,拉低帧率。

… …

鉴于上述问题,本文所讨论的植被方案其主要的核心在于不使用GameObject实例化植被,而是自己管理植被相关数据,并向GPU提交适当的绘制请求。该方案的核心思路为:

对大世界中的植被根据合适的范围大小进行分块,每一块中的植被被分类合并单独保存为序列化数据,游戏中流式管理这些分块数据对象的加载和卸载;对于合适的硬件,植被系统使用ComputeShader对当前场景中存在的植被数据进行裁剪和Lod计算,然后绘制通过裁剪的植被数据;如果硬件不支持使用GPU剔除,则通过四叉树结构对植被分块数据进行裁剪和Lod计算,然后绘制通过裁剪的植被数据。2.分块管理2.1植被分块

植被数据的分块可以根据大世界加载范围和视距确定一个合适的范围大小,所有处于该范围的植被数据被保存在同一个序列化文件中。例如,如下如所示,以200m*200m分块,每一块中囊括一定量的植被数据,图中的绿色Box代表分块中一种序列化植被数据所形成的AABB包围盒,其中有的包围盒可能大于200m,是因为有的分块中植被数量很少或者占空间很小,因此被合并到临近的分块数据中。

下图是对保存的分块植被数据进行预览,其中每一根线都代表一颗植被。

2.2数据序列化

为了让保存出的分块植被数据尽可能小,本方案仅保存渲染植被必要的一些数据,单位植被的数据结构为:

其中,_colorR、_colorG、_colorB用来保存植被的附加颜色属性。

对于分块数据中的一种特定植被,通过VegtationPrefab定义,其中,VegtationLod记录该植被的lod层级对应的mesh、material等相关资源。

经过保存的植被数据在编辑器Inspector中,如下图所示:

一个植被数据绑定在一个确定位置和覆盖范围的GameObject上,与大世界中其他物体一样被管理,在运行过程中,大世界加载系统根据需要加载位于主角附近的植被区块数据,然后植被系统使用这些数据进行必要的裁剪工作,并组织渲染。

2.3运行时数据

由于压缩序列化文件体积的需要,本方案并没有直接保存每一单位植被的Local2World矩阵,因此当植被数据被加载上来时,需要将保存的数据转为Local2World矩阵,同时,为了保证向GPU传递的数据尽可能小,可以将颜色等附加信息编码进Local2World矩阵的第四行中。

上图中PackColor32函数的目的是将Color32颜色信息使用float保存。

在shader中,利用上述矩阵,组织渲染必要的其他数据。

同时,为了方便运行时合并多个植被数据中的同种类植被,这里也使用各级lod属性在运行时的实例ID组合来作为VegtationPrefab唯一性标志,前文RuntimeInit函数中m_runtimeMapID就是做这个事情的。

3.GPU驱动裁剪与渲染

在实机运行起来时,需要根据设备情况选择使用GPU(ComputeShader)还是CPU来驱动剪裁。

3.1运行时合并数据

如果使用ComputeShader进行植被裁剪,已经加载到场景的植被数据会根据上文所述唯一的m_runtimeMapID合并到VegtationPrefabRuntime类中。VegtationPrefabRuntime类代表运行时的一种植被类型,裁剪和渲染相关的具体工作都在该类中进行处理。

3.2裁剪与LOD计算

无论使用ComputeShader还是CPU对植被数据进行裁剪,都需要通过当前帧视锥信息计算6个裁剪平面。

除此之外,还需要向ComputeShader传递一些其他的必要裁剪属性,例如渲染距离,以及HiZ属性等。

一切都准备好之后,就可以对所有种类的植被进行裁剪计算了。

上面的代码中,会先对植被数据进行一次包围盒和视锥的相交计算,如果无相交,则不需要处理。然后,调用ComputerShader进行裁剪计算,并对通过裁剪的植被单元填充到ComputeBufferType.Append类型的ComputeBuffer中,然后使用DrawMeshInstancedIndirect API对通过裁剪的植被进行绘制。

在ComputerShader中每一个植被的矩阵数据要经历这样的计算过程:

根据植被到摄像机的距离确定要使用的包围盒参数和目标Buffer使用裁剪平面数据判断植被是否处于视野中使用HiZ判断植被是否被其他物体遮挡若前三者都通过则填充到对应的目标Buffer中,供Instancing绘制使用。

经过上述视锥裁剪、HiZ剔除、以及LOD计算,可以大大降低最终被渲染的植被数量。如下图所示,每一帧只渲染处于视锥体之中,并且没有被摄像机前的物体遮挡的植被单元。

CPU驱动裁剪与绘制

在不能使用ComputShader进行裁剪的情况下,最方便的方式是将每一分块的植被数据构造四叉树结构,每一个四叉树节点保存处于节点中植被数据所形成的AABB包围盒,运行中每帧对四叉树使用GeometryUtility.TestPlanesAABB API不断向下进行裁剪计算,对处于视野中的叶节点根据包围盒中心到摄像机距离确定应该使用哪一级Lod进行渲染,最终根据植被种类合并到对应的VegtationPrefabRuntime_CpuCull实例中进行渲染。由于Graphics.DrawMeshInstanced API有一次绘制1023个单位的限制,因此这里使用RenderBatch类专门进行处理。

由于四叉树的构建有较大的性能消耗,因此最好将四叉树跟植被数据一样提前生成好,并保存在导出的植被序列化文件中(VegtationPrefab类中的m_treeRoot)。下图中每一个绿色框为离线生成好的四叉树的叶节点,其中的数字为叶节点中包含的植被单元数量。

这种方式相比ComputeShader,最终渲染的植被数量会稍微多一点,同时,植被显隐和切换是一块一块的,因此视觉效果和性能都会稍差一点,但是兼容性和稳定性更好。

欢迎加入我们!

感兴趣的同学可以投递简历至:[email protected]



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3