游戏开发论坛

 找回密码
 立即注册
搜索
查看: 3664|回复: 0

基于Unity3D引擎的大地形加载研究

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2019-11-15 14:26:54 | 显示全部楼层 |阅读模式
在之前的几个月中,本人开始着手探索Unity3D引擎在主机PC平台的一套比较高效的渲染和加载方式,到目前为止已经实现了基本的渲染方法,这里先放上主要的实现参考,GDC 2017 Ghost Recon Wildlands Terrain Tools and Technology:

Ghost Recon Wildlands Terrain Tools and Technologywww.youtube.com

前排警告:这篇文章,非!常!长!而且因为技术点琐碎,描述难免有些伦无语次!希望诸位看官见谅!

本篇文章我们将会从以下几个方面讲解在Unity3D中搭建一套以高端平台为目标的,理论支持无限大的平坦大地形实现,为什么要特别强调“平坦”呢,因为这里实现的地形是指狭义的使用高度图对平面进行置换的渲染方法,因此类似山洞,峭壁,树木等等暂时不在这套系统的实现范围内,在本文中也将不予讨论。我们将分为以下5个部分讲解这套大地形的原理和表现:

1.        基本渲染实现

2.        资源的加载和卸载

3.        地形着色与生产流程

4.        性能表现

5.        总结

基本渲染实现:

我们采用了曲面细分 + 高度图的方法进行渲染。先来说曲面细分,每一片地形会经过DX11以后支持的Hull Domain Shader,经过数倍细分,分成一张三角形均匀排布的平面:

image002.jpg
x1, x32, x64 的细分

比起传统的网格绘制,相同面数下曲面细分的性能表现非常优越,首先其光栅化性能比单独的三角形光栅化要高数倍,在PC平台进行压力测试时,我们发现同屏细分几千万面,每帧的耗时也只有寥寥数毫秒的变动,这令我们感到十分惊喜,并直接决定使用Quad Plane + Tessellation代替传统的GPU Instance方法。同时Tessellation最高支持64级细分,这样每两个三角形就能承担起64 * 64分辨率的高度置换图,这也让高度的精度选择十分灵活,大大增大了制作的自由度。

在LOD方面,我们使用了四叉分割树的方法进行计算:

image004.jpg

四叉分割的算法是大学数据结构课里比较基础的部分,这里就不再赘述。从图上不难看出,地形资源的存储趋近于一个金字塔型,与纹理贴图中常用的MipMap非常相似,实际上两者原理本来也是十分接近的,区别只是地形中每个“像素”位置储存的并不是颜色而是这一整块地形的所有信息,四叉分割树的特性对每一部分的影响在我们本篇文章中都会有所涉猎。

目前整个地形用到的所有贴图都是基于Virtual Texture的,我们在上一篇文章中已经讲过Virtual Texture的实现:

MaxwellGeng:Virtual Texture Tools & Practices

高度图也不例外,所以Shader中读高度图进行置换的方法十分简单粗暴:

image007.jpg

地形使用的Fragment Shader中进行着色的部分也和普通Standard PBR Shader几乎没有任何区别,唯一的区别可以说就是把读取普通贴图换成了读取Virtual Texture:

image009.jpg

因此可以看到,Shader在渲染中基本没有做任何事情,Virtual Texture的生成才是渲染最主要的部分,Ghost Recon中是用这样的办法进行材质生成的:

首先将32套PBR材质贴图存到Texture Array中,然后用一张8位的Mask Map进行选取采样,采样出的效果大概如此:

image011.jpg

进行手动Bilinear,使采样效果变得平滑:

image013.jpg

因为Ghost Recon中并不是“同一块”地形,或者说,不同区域的地形美术风格不一样,而所有区域的材质数量加起来远高于32套,因此他们还使用了一个Indirect Buffer做索引跳转,相当于开了一个对象池实时的卸载掉那些在这个区域内不可能用到的材质,并加载这个区域内一定会用到的:

image015.jpg

这个实现实际上与游戏逻辑比较耦合,这里我们暂时不实现Indirect Buffer,这方面会在之后的材质流程部分实现。

所以我们这里使用Mask进行混合的实现如下,首先封装一下Mask Texture的Bilinear Interpolation,毕竟Mask也要全图连接,所以也是一张Virtual Texture:

image017.jpg

用计算出的权重进行混合:

image018.jpg

但是纯线性的Bilinear会让整个地形充满丑陋的锯齿感,这里用一张噪波图扰动一下即可,当然噪波图怎么生成或者噪波算法怎么写属于制作内容,在之后的部分中我们将慢慢展开。Compute Shader Kernel直接调用这几个方法:

image020.jpg

混合效果:

image022.jpg

其实这里可以看到边缘的混合已经比较平滑而且不规则了,但是搭眼一看格子感依然非常严重,所以这个锅可以说并不是采样的锅,而是材质的处理依然有问题,所以这个问题也会在之后的制作部分讲解。

在有了基本的地形VT的加载渲染之后,我们需要让地形变得可以互动。所谓互动就是有来有回:有来,指的是任何物体的Shader都可以使用绝对坐标获得Virtual Texture的相对坐标,这样在实现一些高级特性时会十分容易,比如沙石交融效果的示例图:

image024.jpg

在这张示例图中,很明显石头是一个模型,而这个模型的材质却需要和地面完美融合,在传统的地形系统中要想实现这套系统不仅非常麻烦,而且很可能造成额外消耗:如果使用Pixel Offset,就会破坏Early Z,在某些硬件上甚至可能导致性能骤降,如果使用二次叠加,那石头的Shader可能会过于复杂,而且功能耦合十分严重。而对于Virtual Texture Terrain来说这基本可以说不是问题了,我们只需要将世界坐标到UV坐标的转换变量作为Uniform传递进Shader:

image026.jpg

注意,这里有个细节,因为考虑到地形非常庞大,因此浮点精度损失在所难免,我们就在C#脚本中统一使用64位双精度浮点数进行运算,并在传入Shader时将整数部分和小数部分分开传入,因此这里多了一个floor frac叠加的策略,不仅如此,还有一个TerrainVTOffset变量同样也是为了解决浮点精度问题,当摄像机距离世界中心非常远(如5km以上),这时客户端游戏逻辑就可以触发地形偏移,将整个地形向世界中心方向偏移,那么浮点精度问题也就自然而然解决了。(当然,这种平移全图的操作目前仅限单机游戏考虑,涉及到网络部分超出本人能力范畴,不在此过多讨论)。

这里随便摆个球测试一下混合效果,看起来混合上完全没有问题,使用世界坐标简单粗暴的插值两套贴图,实现方法和使用二套UV读贴图混合并没有本质区别,只是UV变成世界坐标而已,从这里也可以看出Virtual Texture强大的鲁棒性:

image028.jpg

互动的“来”已经有了,接下来是“回”。除了能够对外输出图片以外,地形系统还兼容了上一篇文章中讲到的贴花系统,之前也同样讲了Virtual Texture的贴花是基于正交摄像机投影的一套独立的非常轻量的管线,相当于Deferred Shading的Geometry Pass,只不过输出的目标不是屏幕而是VT而已。在地形中我们分了两个Rendering Pass,一个负责Albedo等PBR贴图的输出,另一个负责Heightmap,这是因为PBR贴图的分辨率一般要比Heightmap要高,所以不可能通过单个MRT Pass输出。我们先写一个最简单的置换输出Shader,这个Shader的用途是让地形隆起一块:

image030.jpg

image032.jpg

Shader的实现十分简单,毕竟只是个渲染测试,这里直接上代码:

image034.jpg

首先,调用SRP 提供的API,渲染所有RenderQueue中的TerrainDisplacement Pass,并且设置当前高度图为RenderTarget,这里要凸起而不是挖坑,所以混合方式标记为BlendOP Max,当然这也可以不用,因为为了考虑到可能有更加智能的混合方式,这里对贴图做了Double Buffer,也就是说Frag Shader可以一边读一边写,也就上方_VirtualHeightmap和_OffsetIndex这两个变量的用途。最后,用传入的比例,从世界坐标反推出高度图的数值并输出,最后输出结果就可以直接映射到地形的高度图上。

但是,这种贴花置换的方法简单暴力,也十分突兀,因此我们非常需要让贴花的混合变得科学柔和一些。这里提供一种最常用的材质混合方法:高度混合。这里熟悉贴图制作软件如Substance Designer的美术同学应该会比较明白,高度混合是做这类地面混合时最常用的方法之一,即上边的盖住下边的,上下两种材质在结合处给予一定的混合比例,譬如埋在沙土里的砖,就可以制作出沙土和砖两种材质,并通过两者的高度进行混合,效果往往会比较理想。

当然,具体怎么应用也同样会在制作部分讲解,这里只讲原理,基本原理是使用Alpha Blend,并读取Virtual Texture本身的高度,以此计算出顶点高度和地形高度的差,用这个差当做混合权重输出到Alpha通道中,即可达成目标。

首先是获取混合权重部分:

image036.jpg

这一部分比较容易理解,是用世界坐标作为统一的坐标系,这样在编辑阶段比较友好,然后计算两者的差并用材质中的混合权重相乘。

最终使用高度差计算出的权重,将会被用于混合PBR部分,以及高度本身:

image038.jpg
混合PBR贴图

image040.jpg
混合高度图

经过高度混合的贴花就不像之前那样,平地立起一个蛋非常突兀,相反会变得平滑不少,在交界处也有了柔和的混合:

image042.jpg

到此,渲染部分的框架基本搭完,接下来需要一套合适合理的资源管理系统,毕竟我们本篇文章的题目是“加载研究”,因此渲染还是需要在资源管理系统的加持下才能工作。

资源的加载和卸载:

对于开放世界游戏中的大地形来说,最突出的资源管理的矛盾无非是两点:从硬盘加载的迟缓和有限的内存容量的矛盾,分布式生产开发和要求统一的数据结构。

第一点的矛盾在于开发者必须制定好一套合理的流式异步加载方法,走到哪,加载到哪,并且加载的过程需要润物细无声,对主线程的影响最好微乎其微,这是比较难解决的一点。第二点就是整个地形必须要保证数据结构统一并且强关联才可以做到无缝,但是开发过程很大概率是多个小分队一支队伍负责一小部分,这就要求多个团队分布开发的资源需要能够无成本合并。

这里我们为了解决这两个问题,使用了这样的开发思想:整个地形以一个完整的四叉树格式进行存储和读取,其中最大的可渲染地形块永远是1km,LOD每高一级边长缩为1/2倍,体积缩为1/4倍,而相对分辨率也会翻倍。

举个例子,整个地形8KM,那么最初的等级就是8KM,接下来是4km和2km,这三个等级都完全不会被用来渲染或者做什么事情,它们只是为了方便更高等级的LOD遍历而存在的根节点,当然也没什么需要储存的信息。

接下来到了下一级,也就是1km,这时候已经开始渲染了,这时我们需要加载负责Virtual Texture着色的Mask map,按照Ghost Recon的规格,Mask Map分辨率是 2pixel / m,那么这一张图的大小就是2048 * 2048,当然,由于Mask也和地形一样属于无缝的图,所以我们同样需要把这张贴图一步到位加载到Virtual Texture中,这自然不必多说。在完成整体的Mask Map加载后,开始加载高度图,高度图是每个地块256x256分辨率(当然这个标准随时可以改)。由于高度图是单通道而且不容许任何精度损失,因此我们把所有高度图都打包成完整的二进制,储存在二进制文件中,而这个存储有整个8KM地形高度信息的二进制文件格式就是:

LOD0: 8 * 8 * 256x256

LOD1: 16 * 16 * 256x256

LOD2: 32 * 32 * 256x256

……

LODX: 2^(3 + X)^2 * 256x256

这样当我需要获得某一级的某一个位置的地块的时候,计算的方法就是这样:

1.        使用LODLevel M算出当前Level的总体偏移O:

1.png

2. 将坐标降维并累加到偏移,获得字节位置P:

2.png

这个算法是不是看起来非常熟悉呢?没错,这就是Mipmap的算法,只不过这里把每个像素换成了一整张图,相当于一个4D Texture。

纵观这套算法,可以看到我们基本考虑到了提到的两个需要解决的矛盾。首先是流式加载,加载Mask Map的时间只需要寥寥几帧就可以完成,毕竟一张图也只有2048 * 2048 * 8 bit,也就是4M大小,而这张图将会负责整个1km地形的基本材质,性价比极高。高度图的加载效率也非常高,使用纯二进制文件加载,对主线程的唯一影响就是在提交渲染之前把分线程的数据提交到显卡这个过程需要同步,这个过程用时在0.1ms以内,除此之外并没有任何多余消耗,可以说就是纯粹的从硬盘读取的过程,没有什么中间商赚差价。

同样这种方法对于解决团队分布协作问题也比较容易。在开发时先从DCC制作出一整块1km地形的最高级LOD的高度置换图,当然,由于分辨率很高因此肯定不可能是一张图,我们可以让DCC给每张图命名为HeightMap_X_Y,其中X和Y表示其所针对的地形块,在Editor中使用工具把每张图序列化到指定为位置,原理则是使用上方的公式计算偏移并使用FileStream输出即可,我们目前也有一些测试性的小工具,这种实现十分简单没必要详细展开讲解。

到此我们就有了一个1km的高精度的地形高度存储在硬盘里,接下来就像普通的计算MipMap的方法一样,把上下左右4张贴图分别贴到一张2倍分辨率的RT,并降采样到原分辨率,回读到内存中并写入到上一级LOD。

这个运算过程只需要几秒钟就可以完成,即使需要手动在DCC中刷高度图,也完全可以刷完后直接在引擎中运行并看到效果,可以说这就解决了第二种问题。

从这张宏观俯视图就能看出这套资源管理的加载效果,第一张是材质的分布,这里故意用了4张完全不同算法的随机噪波图,因此中间有明显的分裂感。第二张则是每块地形的UV分布:

image044.jpg

image046.jpg

接下来是贴花的加载。既然贴花是普通的Prefab,GameObject,那么加载上就不需要像上边这样大费周章开发一套二进制方法了,而是使用引擎提供的资源加载即可。我们使用Addressable提供的API进行异步加载可以非常方便。

Addressable除了提供Load Unload,还提供了InstantiateAsync这种API,它可以自动进行异步分帧的实例化操作,十分方便,加载时只需要在协程中调用即可,这一点还是要吹一下Unity官方提供的这个Package的,当然它的不足之处也不是没有,它使用了引用计数的方法标记实例化资源,这就意味着靠这个API实例化出的GameObject最终不可以用普通的Destroy来销毁,而是必须使用这套系统提供的API,在开发项目时必须严格注意这种容易留坑的部分。

贴花部分的加载逻辑和地形四叉树这种加载是完全不同完全独立的两套系统,贴花的加载我们称之为“保守式加载”,所谓保守式与四叉树这种激进式是对应关系。对于地形高度图和材质遮罩,我们的加载思路是,从高级到低级LOD采用降采样的方法,让高级LOD从显存中“当场去世”,马上被回收掉。而从低级到高级LOD时也是类似的方法,在加载之前没有任何缓存,直接从硬盘中加载并替换到低级LOD的位置,低级LOD也瞬间被干掉。这种方法称之为“激进式加载”,因为加载过程前没有任何缓冲,加载之后也没有任何缓存,手起刀落眼都不眨一下眼睛也不干。之所以对于地形要激进,原因无非有二:资源不具有复用性、缓存成本远高于就地加载。资源不具有复用性是因为玩家在场景中的移动轨迹不确定,加载高级LOD时上一级LOD很大概率在相当一段时间内不会被用到,而且即使重新加载可能也只需要数毫秒,相比于占用宝贵稀缺的内存空间,重新加载是更聪明的方法。

而对于贴花,我们则使用保守的加载策略,即大量缓存,比如在2km之外就开始异步加载这一个区块内可能用到的所有的贴花,当离开这个区域时干掉一些基本在其他部分不可能用到的贴花,并且保留一些可能用到的贴花的实例。与地形高度图不同,贴花具有很强的复用性,像小石子小土屑这些可能一张图只需要256x256分辨率,而一张2048x2048的Atlas就可以容纳64张贴图,同时,不同的模型也可以共用同一套贴图,比如都是土块,形状大不相同,但贴图复用,无论从视觉效果,还是生产效率,都是最优的做法。

因此我们可以从1km的LOD0或512m的LOD1开始加载所有贴花,并且对贴花使用LayerMask进行分级,对于远处和近处的地面使用不同的Culling Layer,进而实现精度的差异,这里在配置文件中设置不同LOD等级的Layer:

image047.jpg

这里再拿刚刚用过的那个球来演示一下:

这里首先把这个卤蛋一样的贴花存成Prefab并标记为Addressable:

image048.jpg

image049.jpg

这里在完成制作时直接把整个区块内所有的贴花GameObject全部打包到同一个Prefab中即可,在接近区域时直接对一整个Prefab进行异步实例化,不需要担心传统Instantiate函数的卡顿问题,堪称一键傻瓜式操作。

这种保守的加载方法好处和坏处都十分明显,好处是其加载几乎完全不拖累地形的加载,因为可以认为当地形需要被加载时,贴花就已经在原地等着了。当然坏处也很明显:内存占用肯定比激进的加载卸载方式更高,同时游戏开始时可能存在读条时间更长的情况,这需要考虑项目的实际需求进行权衡。

地形着色与生产流程:

在第一部分渲染部分中完成了引擎逻辑的实现,在本章节中我们将完善着色部分和生产流程对接部分。

首先迫切需要解决的就是法线显示的正确性问题。在加载高度图时,此时法线依然是平面的法线,也就是(0,1,0),相当于在计算法线是并没有考虑到TBN Matrix,所以看起来整个是平的。法线的标准非常重要,因为法线在出了问题后很可能一时半会不容易发现,比如某个轴反了等等。曾经笔者的一个朋友就搞混了DX和OpenGL标准的法线标准,结果惨遭无情嘲讽。这是生产过程中绝对是不允许的。

image050.jpg

因为地形是依靠高度图置换的,因此其朝向可以认为永远朝上,所以我们就可以认为默认的Tangent永远是(1,0,0),这样对高度进行求导,获取到置换后的Normal,计算置换后的Normal与Tangent的叉积,就获得了置换后的Binormal,再利用置换后的Binormal和Normal获取到置换后的Tangent,代码如下:

image052.jpg

这样,法线的坐标转换放在了贴花计算之前,之后贴花输出的法线将直接提供世界坐标的法线,这样就可以保证材质的法线和贴花的法线标准统一,正确的法线坐标下可以看到有很明显的凹凸感了:

image054.jpg

标注统一成功后我们就要开始着手材质的实现,毫无疑问使用贴图记录材质索引的方法会导致边缘处完全没有任何过渡,唯一的过渡就是Bilinear Interpolation提供的那点微薄的贡献,这时肯定有美术同学问“为什么不使用传统的Blend Mask”,首先一张Mask图片最多只能提供4个通道,而我们一块地形很有可能用十几种材质,那么Mask就会严重限制材质发挥,其次,在混合时效果也有比较严重的问题,在TGDC上 Trace Yang 老师也提到过这一点,一个绿草材质和一个黄沙材质混合,会得到一个黄色草地,而显然黄色草地这种材质是并不存在的,也就是说使用Mask Blend进行材质混合的表现,是具有不可控性的。

我们的方法则是构建多个复用同一套或同几套贴图的材质,在生产材质时,每一套材质都具有Albedo, Normal, Smoothness, Metallic, Occlusion, Height这4个属性,其中Height,或者称之为Displacement,与Smoothness, Metallic, Occlusion一起放置在同一张R8G8B8A8_UNorm格式的Render Texture中,8位的精度对于材质混合来说绝对够了。那么之后的混合原理也非常简单,就是简单的计算两套贴图的Height的差,并按照一定的比例进行混合,而传入到GPU中的每个材质的数据则是:

第一套贴图索引,Int32

第二套贴图索引,Int32

混合比例,Float32

高度偏移,Float32

原理很简单,但是必须要有一个合适的工具来完成这个事,毕竟调整材质这种事情需要所见即所得,这里拿出两套材质:

image056.jpg

image058.jpg

在工具中允许开发者将多套贴图和材质缓存,实时调整材质属性并直接看到材质表现:

image060.jpg

使用这样的方法,我们可以按照索引顺序从上到下让材质依次从以泥土为重到以草地为重,构建多套材质,使得相邻的索引总是相似的材质,并且混合比重按照索引的序号进行单方面步进,这样比起传统的Mask方法好处在于两套贴图的混合效果永远在人的控制范围内,并且具有很高的复用性。

这里我们整个地形不用任何其他贴图,就单纯用这两套贴图来做个实验,当然要多定制几套混合材质:

image061.jpg

混合的结果:

image063.jpg

image065.jpg

可以看到,即使在只有两套贴图重复感非常严重的情况下,边缘的混合也显得非常自然,因为整个混合过程都在工具的控制范围内,而Index map的生成这里是直接使用了一张很普通的FBM Noise Map,没有做任何特殊处理,这意味着在接下来的材质生产过程中,材质制作的时间复杂度是O(logN)而非传统的O(n),只要一次性保存好所有的材质属性,之后每一块地形都只需要提供一个大致的Indexmap,并对边缘进行均匀采样模糊等程序化操作,即可实现图中的柔和混合效果,似乎无论从运行性能,开发效率还是表现效果看,基于Virtual Texture的材质索引混合方式都是要优于传统的Mask map的方法的。

前边同样提到过Mask map是放置在Virtual Texture中的,这就意味着我们可以很轻松的实现实时绘制,因为VT本身存储容器就是Texture Array,只需要从字典中查找到对应的地形块所使用的Texture Element,即可直接设其为Render Target,完成绘制,这对于未来的PCG方向非常有帮助,对美术操作也比较友好,比如这里我们写一个简单的笔刷,还是上方这个例子,土地部分是1,草地部分是0, 中间穿插数个高度混合插值的材质:

image067.jpg

直接两种材质混合极为生硬,所以我们就多调整一下笔刷,在边缘处多刷几种混合材质,让表现自然起来:

image069.jpg
红箭头处其实在手刷时有明显交界,但是已经几乎看不出来

在确定了材质生产的问题后,我们还要确定另一个问题,那就是地形的精度和性能的取舍问题。Virtual Texture在大大解放了地形材质复杂度问题时,同时也带来了沉重的显存压力,所有地块使用的贴图均为不重复贴图,且更新极为频繁压缩运算成本很高已是客观事实,所以我们在控制地形LOD距离和大小切分时,必须慎之又慎,才能平衡显存的占用,渲染的消耗和效果的表现,这不仅仅是美术的问题也是策划的问题,毕竟是与游戏表现和逻辑息息相关的。这里我们还是启用UV Map的方式,并且将地形高度设置为0,能够更清楚的看清地块的分布:

image071.jpg

在测试场景中,最高级LOD的地块边长为8m,而整个地形长2048m,1024pixel/8m对于大多数高端平台游戏来说是可以接受的,然而这样的设置显存占用却高的可怕,单纯加载摄像机周围一圈的地块,对象池内最高贴图占用可以达到160张,单纯显存占用就已经高达1.55G!按照中端PC显卡8G配置,这已经很难令人接受了。我们的优化思路有两点,首先,对Virtual Texture能提供的贴图数量限制死,并且在四叉分割时进行判定,如果当前池内已经没有了足够的贴图,就放弃LOD细分,这样即使池内容量不够,地形还是会正常工作,只不过表现的精度会有所损失,这种做法增强了功能的鲁棒性。其次是从摄像机的角度下手,对于大多数游戏来说,很难出现一直在不停的旋转的情况,因此对摄像机背后的地块“偷偷摸摸”的降低LOD等级,并在摄像机转过去(或者将要转过去)的时候再使用高标准LOD进行加载和渲染,这种方法在GDC的演讲中也多有提到,可以说是一种无奈的妥协之举,毕竟即使是高端平台容量方面也依旧是不容小觑的短板。

在测试中,我们假设玩家站在地上,以脚的位置为地形的Y轴坐标,使用摄像机朝向方向和摄像机坐标到地块位置坐标的方向点积,判断摄像机是否朝向地块,对身后的地块采用0.33倍的LOD距离,也就是说本来该加载30m的地块现在只会加载10m就回退到上一级了,到此我们已经将总贴图占用控制在了128-133块,而精度则完全没变。这一部分我们是这样考虑的,我们计算地形的LOD等级时,使用的是摄像机到地形边缘的最近距离,而非到中心距离,所以实际0.3倍左右距离并不会导致LOD差异很大这样的问题出现,因而这样的优化和平衡是可以接受的,唯一的缺陷就是快速移动+转头时能感到一瞬间(3-5帧)的加载迟滞,这一点属实无奈,在进行这样的权衡时非常需要策划和程序进行大量的儒雅随和的沟(si)通(bi)。

性能表现:

性能测试中,我们使用了一台搭载有公版GTX1070的测试机,使用的规格如下:

地形总大小/总可视大小:2048m * 2048m

Material Index Map: 2048 * 2048 / 1km^2

HeightMap: 256x256 / 8m^2

因为使用了简单粗暴的四叉分割,地形可视距离有限,因此即使理论可以无限大,实际肉眼能看到的距离也在2km左右,所以“如果再大点性能会不会差”这样的可能性从原理上是不会发生的。

我们对性能的测试结果为:

四叉树计算:<0.1ms

所有加载:< 1.1ms

绘制与提交:< 0.2ms

加载整个过程是耗时最多的,但是上方表现的也只是测试中加载耗时的峰值,实际大多数帧中加载任务并不会很繁重,加载任务也如前半部分文章讲的采用了均匀的分帧和多线程手段基本杜绝了主线程性能栓塞,我们认为这样的性能表现是合格的。

实际对于加载来说,更高的消耗往往还是由上面讲到的各类基于Virtual Texture材质贴花,置换贴花,在渲染绘制方面,之后文章中可能会涉及到的草地,灌木,树木等植被渲染,峭壁石块渲染,这些特性同样会给性能带来不小的压力,因此我们的性能测试环节将会持续跟随到每一部分。

总结:

一句话总结我们整套地形的技术特点就是:完全基于Virtual Texture和四叉分割树,使用预制作的材质属性和材质索引贴图进行实时加载和着色的高度置换大地形。这套实现方法较为新颖,在技术上比起一些老的渲染方式也有一些提升和增强,配合GPU Driven Rendering Pipeline,可以大大提高开放世界场景渲染的复杂程度。当然,Ubisoft分享的地形系统还有许多其他的技术热点,这些在之后的文章中将会逐步推出,本篇则专注讲解了基本的地形加载部分,其他部分并未太多涉及,还请诸位谅解。


作者:MaxwellGeng
专栏地址:https://zhuanlan.zhihu.com/p/85417843

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-11-20 22:38

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表