在 上一篇 中,主要覆盖的内容可以概括为动态的网格策略、不一样的多层材质策略,以及如何应用虚拟纹理来渲染地表。这次继续读这篇分享的下半部分, 还是以翻译原文PPT页及解说稿为主,打星号的部分则是我个人的补充。 *作为和上篇的衔接,会先从虚拟纹理(缩写为VT)页的组合回收策略开始。
每一帧,我们只计算有限数量的页(page)。在低端移动端硬件上,这个数量不超过4页;而在高端PC上,每帧的数量是20。
而不在缓存中的最重要的页,则始终被最先合成。(*原文中是Popular这个词)
如果还有剩余空间,我们重新计算可能用到的低精度的页——它们的生成也会按优先级排序。
所有页都有一个寿命(age)。被请求的页会将age重置为0,而不可见的页会随时间递增这个值;超过3帧未被请求的页会被回收,而回收始终从最旧(age最大)的页开始。
贴花 (Decals)在渲染大地形中扮演重要的角色。
地形贴花 ——顶视角,矩形正交投影(top-down rectangular orthographic projections)。
样条曲线(splined)贴花 ——顶视角,沿着一个 贝塞尔曲线 矩形正交投影(follow a Bezier curve)。
体积贴花 ——不限于顶视角,对于任意几何体都有效,不限于地形。
我们把负责从硬盘加载资源的系统称为 streamer (流式加载器)。它的策略通常是基于摄像机的位置。
例如,streamer负责加载(全屏幕分辨率下)贴图对应的mip;它也负责加载网格的LOD,以及声音、动画等其它类型的资源。
大地形需要从硬盘加载大量的数据。基于摄像机位置,游戏计算出网格的顶点和索引、贴图mip、地形层级和贴花等需要加载的数据。
当一个页被请求合成时,游戏首先检查是否有合适的mip被加载。如果未被加载,则提高它们的加载权重,使其能尽快被加载完毕:
如果有贴图mip未加载,我们仍合成该页,但标记其为 低精度 (low-rez)。
如果关键数据未加载——例如贴花节点,我们则标记该页为 未完成 (not ready)。
渲染有着未加载元件的页会导致显著的视觉故障。
如果没有元件缺失,我们则标记这一页为 加载完毕 (ready)。
地形(渲染的)合成使用3层被称为scratch buffers的G-buffer。(*scratch没有合适的翻译,直译是划痕)
第一层存储了 漫反射系数 (albedo),第二层存储了 法线 (normal),第三层存储了 金属度、遮蔽度和粗糙度 (metalness, occlusion & gloss)。当合成一个页时,会同时写入这3个buffer。
Scratch buffers是未压缩的渲染目标(render targets),因而很耗显存。
*render targets可以理解成是临时渲染的一些中间结果,用于后面的合成和渲染步骤。它们不会直接显示在最终的屏幕上,但会占用相应的显存。
我们首先使用一个compute shader(计算着色器)来渲染地形层;
之后用一个vertex & pixel shader(顶点-像素着色器)来渲染样条贴花;
再之后通过一个vertex & pixel shader来渲染地形贴花;
最后用一个compute shader来渲染体积贴花。
贴花彼此之间是排序过的,但(基于管线)无法使一个体积贴花先于地形贴花来渲染。
*compute shader对于低端的移动设备还是不兼容的。
在把scratch buffer中的页复制到物理纹理中前,我们需要对其进行 压缩 ——这也通过我们编写的compute shader来完成。
在移动端,我们使用ASTC压缩,而其它平台我们使用BC压缩格式。在除了移动端外的其它平台,数据可能被压缩成 2张BC3 纹理、或 3张BC1 纹理——注意这是我们目前为止可见的最大视觉质量的损失。
*BC是block-compression的缩写。两种压缩方式都是的解压插值计算都由对应的GPU来支持。文末附带了BC压缩格式的说明。
*这里虽然是翻成物理纹理,实际上应理解成“最终合成的地形纹理”。
我们的物理纹理有2层mip——这会在纹理的双线性插值中使用到。
而我们的物理页有4像素的边缘,这是为了使8X的 各向异性过滤 能正确工作——注意,这意味着我们每页只能使用248*248像素(预留了边缘)。
The first texture encodes albedo and metalness, while the 2nd encodes normal, occlusion & gloss. The X component goes in the green channel while the Y component goes in the alpha channel.
在BC1压缩模式中,我们有9个通道(3张)供使用:
法线的分量存储在绿色通道中,是因为绿色通道有6bits的存储精度,而其它通道只有5bits。
虽然理论上BC3模式应该有着更好的视觉质量,但我(作者)从来无法区别两者的差异——而BC1模式节省了三分之一的显存,并且添加了一个通道,因而是更好的方案。
*这里绿色多1bit是因为人眼对颜色RGB的感知本来也不是均等的。
在开启了GPU 曲面细分 和 置换贴图 (Tessellation&Displacement)的平台,我们也使用虚拟纹理来存储置换贴图。
一个置换物理页是32X32的尺寸,有着1层mip以及1像素的边缘——因为它只用来做顶点的位移,而顶点的密度明显远小于像素的密度。另外,它使用BC4的压缩,因此内存占用非常小。
需要注意置换贴图不能影响碰撞(collision),这意味着它们不能做得太大,以至于导致明显的显示问题。
*Displacement是一种以纹理数据描述运行时顶点形变的技术,只修改渲染时的顶点位置。作为对比,法线(normal)机制只影响光照着色,并不改变顶点位置。
由于我们的地形技术需要运行在各种不同的设备上,因此AVT(上篇介绍过,自适应VT)参数可以调整以应对不同平台。我们有5档渲染质量——参数可以参考上图。
VT pages for the top 6 levels of the mip chain are baked offline. Usually, these pages are quite expensive as they cover large swath of the map.
最高6个级别的mip链是离线烘焙的。通常,渲染这些页的开销过高,因为它们覆盖了大范围的画幅。通过离线烘焙这些纹理,我们显著降低了GPU的压力,并确保视觉质量。
它对于任何很靠近摄像机的物体来说无疑精度不足,但在一定距离看来,离线烘焙的纹理对于GPU方面的优化是更好的方案。
Although these images have to be streamed, it ends up saving memory, because we don’t need to stream terrain layers & decals for terrain surfaces that are sufficiently far away. This saves memory and reduces streamer pressure.
尽管这些纹理需要被stream加载(而不是GPU合成),它仍能节省显存,因为可以略过很远处的地形层和贴花的加载过程——这最终节省了显存并缓解了streamer的压力。
当使用BC1压缩模式时,一共有9个可用通道——这意味着能多出一个通道用于其它渲染数据(前面也提到了)。
散射 (Scattering)通常用来渲染雪,使它看起来更真实——如图所示。
热量 (Thermal)用于夜晚和热成像(heat vision)。
发光度 (emissive,直译是放射、辐射度),你可以在地面放置琥珀等宝石。
启用这类特性是在地图级别的,我们只支持一个地图同时启用一种额外特性。(*因为就一个通道)
当每个地形表面有各自的 高度图 (heightmap)时,要得知大地形上任意一点的高度就变得困难了;而当一整个高度图用以覆盖整个大地形时,这个问题就变得微不足道了。
我们将其称为 一体化高度图 (unified height map)——缩写为 UMH ,因为我们将所有地块的高度图合并成一个了。
基于UHM,实现 边缘环绕 (skirts,直译就是裙边,如图)效果变得可能了——而这是我们的艺术家一直想在地形上做出的效果。
一个传统的UHM需要消耗32MB的内存,而艺术家认为如果有128MB或更多,他们就能做出描述更精确的地形。而32MB是很多平台的(单纹理)内存上限。(*上篇提到了,memory这里同时是内存和显存的概念)
通过将UHM虚拟化,我们显著减少了内存上的限制。我们将这项技术称为 虚拟高度图 (Virtual Height Map),缩写为 VHM 。
VHM有着144页,每页是64X64,以及额外2像素的边缘。这意味着使用的缓存空间略小于1.3MB。
它同样使用了一个 间接映射表 (indirection table),类似于VT用到的那样;不同的是它只是一个buffer(纯数据的)——对于最大的地图,它也只占用8KB。
VHM的页基于和摄像机的距离排序优先级,并且我们始终会填满这一缓存。页存在缓存中有一定滞后量(hysteresis),使其更不容易被释放(ejected,直译是弹出)。
在渲染图中,你可以看到不同的地形块的精度不同。绿色最高,蓝色次之,以此类推。
在项目早期,我们评估了多种超采样技术,以便我们能在内存中加载整个压缩后的VHM。
虽然最终VHM还是要被stream加载,但这些调研并不是毫无成果。
所用这项研究使用的高度图数据均来自美国国家地址调查(United State Geological Survey)主页。
在这一页及后几页演示的这个算法中,P是一个父级纹素(parent texel),需要通过代码来预判4个子纹素的高度。
P是ABCD的高度值的平均值。我们始终从A开始重新构建(reconstruct),之后才是BCD。
Simplest image upsampling technique is to assume that child height is the same as parent height. This is our C0 predictor.
最简单的图像超采样技术会假设子节点的高度与父节点相同,这就是我们在C0步骤的预测器。
We can do something a little more complicated for D, now that we know the value of A, B & C.
对于D我们可能经过更复杂的计算,而我们现在知道了ABC的值(等于P)。这项超采样技术将内存节省至了60%(相对于源数据)。
在C0的基础上我们把A加入了B,AB加入了C的计算中,不过仅带来了微不足道(paltry)的1%的内存节省。
C1预测器把相邻的父节点列入计算考虑,相对源数据把内存节省至了43%。图中展示了我们重新计算A值的方式。
对于D,我们使用ABC的均值。而由于C的位置距离D纹素更远,因此我们本应对其使用更低的计算权重——不过我们目前的实现并没有这么做。
我们也实现了C2预测器——它和C1很相似,除了我们会查找更大范围的相邻格。因此为了重新构建C2,我们使用了连续曲线(continuous curve)而不是直线。
这个方案在USGS(*前面提到的国家地址调查地图)地图上效果很好,但对于我们在游戏中使用的地图没有提供足够的内存优化程度。
我们也尝试通过一个简单的神经网络(neural network)算法来计算,虽然它比C2预测器效果更好,但开销也过大。
In the end, we used gradient descent to optimize a 5x5 kernel.
最终我们使用 梯度下降 算法来作为5X5计算核的优化。(*计算核表示覆盖的像素范围)
然而,它比起Neville递增预测器来说提升也不大——而后者是我们目前使用的算法,因为它不需要机器学习。
使用预测器(predictor)的目的是为了在不同的纹素上预测计算高度,但我们需要重构建的高度是无损的。
So, we save the delta between the predicted value and the true value in an error stream.
因而,我们计算了 预测值 和 原值 之间的差值,存储在一个误差数据流中。
得到的误差数据流非常稀疏(sparse),因而能被简单的压缩技术压缩得很好(压缩比很高)。加上压缩这步就是我们对于高度图的全部处理了。
使用Neville递增算法,在PS4上解压缩68X68的页需要大约1毫秒。显然我们会重估这一情况,并更倾向于采用C1预测器,因为它对于我们的数据来说更快更省。
使用UHM或VHM,我们现在能实现边缘环绕效果(skirts)了。Skirts能使网格与地形无缝融合。
在(这一页)下方的图中,我们能看到石头与地形无缝地混合在一起。
Clutter also makes use of the skirt tech. In the top picture, vertex morphing & color blending are disabled. You can see that the grass actually floats above the terrain.
放置地表杂物(Clutter)时我们也使用了skirt技术。在上方的图中,顶点形变与颜色混合被禁用了,可以看到草地“浮”在地形表面上了。而当顶点形变启用时,草叶就能正确的融入地形了。
地表杂物,例如草,可以被染色以便更无缝地融入下方的地形。
我们使用了一个更低精度的VT用于染色,以获取颜色的均值。
可以逐实例、逐顶点或逐像素来设置染色值,这都取决于(美术资源)内容制作者的需要。
与杂物染色相关的是网 格体染色 。通过以特定规则采样低精度的VT,我们能使同一个网格体(的不同实例)看起来独一无二,更能融入环境。
目前我们正在开发新的被称为 一体化地形表面 (Unified Terrain Surface)的技术。相比于使用多个地形表面的组合,这个方案使用一个地形表面来覆盖整个世界。
它能解决我们之前遇到的很多问题,例如当两个地形表面有着不同的顶点密度,而又是相邻时——这通常会导致地面破皮或有缝(如图),而艺术家需要放置网格体来遮瑕。
通过UTS,就不再有这个问题了——通过它我们可以实现一体化的顶点、颜色映射(贴图)。
*其实不同顶点密度的地形之间也有动态拼合顶点的方案,但也有其局限,并且很多时候做不到视觉上完全无缝。
在进行了一体化处理后,我们进入地形 虚拟化 (virtualization)阶段。虚拟化能解除一体化地形的内存限制——而这是之前地图不能一体化的主要局限点。
Because the index map cannot be interpolated, as each value is quite literally a terrain layer index, we use point sampling to generate the lower resolution mips
由于 索引图 无法被插值,并且每个值都确实是 一个地形层的索引 ,我们使用 点采样 来生成低精度的mip。(*一种无插值的模式,一般纹理默认是双线性或三线性插值)
由于索引图上的每一个像素都只对应 颜色图 上的一个像素,因此对于颜色图我们也使用点采样方式。
最终,索引和颜色图分辨率必须匹配——因为两者就是一一对应的关系。
虚拟索引图目前是16bits,但我们计划将其降至10bits。而虚拟颜色图使用BC1压缩格式。
*这里(包括上篇)中,渲染和数据层的概念词map很多我没有直接翻译成“图”,以避免进一步和贴图或纹理混淆——实际上这个数据结构单独说就是“图”,而且其实它们的意义是有共性的。很多时候概念的意义是靠上下文来保证的,我相信读文章多的一定能理解这一点。
在虚拟化索引图和颜色图之外,我们也虚拟化了 切口图 (cutout map)。
一个切口图每像素仅需1bit,记录该像素是否被剔除了。
在视觉上,(切口图)绝大部分的页都是白色的,除了少数黑色的页,以及少数页混合了两种颜色的像素。
虚拟切口图使用了一张间接映射表(indirection table),类似其它虚拟映射结构。
对于纯白或纯黑的页,我们只需要使用一个特定的索引值来代表。在缓存中我们只需要放入两种颜色组合的页。
数据量已经足够小,我们都不需要考虑降采样的问题了。
由于我们最多只能有254个这样的页,最差的内存情况下(所有都是两色的页)也只需要150KB的内存。而在我们的大地图上,间接映射表的内存通常小于4KB。
Vista UV技术(*上篇介绍的)能使地形在摄像机无论远近位置都有不错的视觉。
The issue is that it relies on camera distance. We alpha in macro details and alpha out micro details when the camera moves away from the surface.
(在一体化地形中)它问题是它依赖摄像机的距离。当摄像机远离一个表面时,我们使宏观细节淡入、微观细节淡出。
这么处理的原因是宏观细节在远处能有不错的视觉贡献,但在近处会显得像素化。
通过虚拟纹理,摄像机距离这个参数就成了问题,因为VT技术过程不知道摄像机位置。
我们不得不通过转义mip级别来模拟出(fake)摄像机距离参数。例如,mip0意味着从最远5英尺的距离观看,mip1的距离加倍,以此类推。
It creates a discontinuity in the mip chain of the VT pages, since now, 2 adjacent mips will effectively have slightly different data.
这产生了VT页中mip链的不连续,而由此两个相邻的mip会实际上有着轻微的数据差。
虽然在实践中我们已经能在很多场合克服这个问题了,但我们仍在探索改进这一问题的方案。
*这里由于没有配图,不能确定这种不连续会导致哪种视觉问题。
对于模拟冰,人们通常会使用一种高性价比的 视差贴图 (parallax mapping)技术,它对于模拟视觉深度有着不错的效果。
视差贴图目前不被我们的地形系统支持,但我们觉得需要支持这项技术。
目前我们正在增加第4个BC1纹理——这会为我们带来额外的3个通道,这样一共就有4个可选通道(*前面介绍过4类,只能用于1个通道)。
散射和闪亮参数通常被用于雪——而目前我们两者只能取其一;相似的,视差通常会被用在冰上——在我们的雪地关卡中。通过引入第4张BC1纹理,这些效果参数就能同时生效了。
我们也在实验程序生成贴花的技术,这样的贴花有着0内存消耗。这能为一些特别单调的地形快速增加一些细节。
Virtual texturing is a static cache, but we believe we could have some amount of dynamicity.
虚拟纹理是一个静态缓存,但我们相信能有一定程度的动态性(dynamicity)。
一个很好说明这一点的例子就是雪地或沙地的脚印——所有前置技术都齐备了,我们只需要试试重计算虚拟纹理上的一块区域(作为脚印贴花的范围)。
近期我们也增加了触发大规模事件的功能,以戏剧性地改变地图的一些区域。对于地形来说,我们希望它的几何体也能被改变。
在这个例子中,一架飞机坠毁到了地面。使用虚拟化映射的技术,实现这一效果变得轻松简单,因为只有受影响的页需要被更新。
作为一个协助开发组,分享人所在的工作室提供的这套跨平台的地形解决方案还是挺惊艳的,考虑了很多方面的具体实现问题。他们在地形的各个层面都大量使用了虚拟纹理技术,在材质混合、地形尺寸、高度图、贴花等方面都所有突破,有很多领先的“一体化”尝试,也没有放弃低端设备上的基础视觉。
评论区
共 1 条评论热门最新