今天介绍的是腾讯天美工作室的三角洲行动项目在地形上的实现方案,原文地址放在文末。
端游对标3A,手游对标次时代手游品质。从端手画质对比来看,端游有树,手游没有,看起来又不像是互通的样子?
这里会重点分析手游部分的地形(虽然号称是端手一体,但是应该会做一些裁剪)
- 手游的方案是在端游的方案基础上,通过一些技术突破(优化)实现的
目前做到了如下的一些特性:
- 10km 的大世界,(移动端)却用到了 32 种材质(Landscape 的方案,通常是 4 种一个
component 或者 8 种一个),PC 上可以用到 256 种 - 每米 512 文素,比 CODM 高一倍
- 是因为用了 MaterialID 方案的原因吗?
- 实现了其他手游不敢做的峭壁
移动端画面比对
性能比对:
- 帧率上,DFM 方案的最高配接近或持平 CODM 的低配画质表现
- 功耗跟带宽消耗上,DFM 的方案远低于 CODM 的方案
- 功耗低是因为带宽消耗低、图片采样数变少?
- 带宽消耗低是因为输入的贴图数目实际上是变少了
技术要点分三块:
多层地形渲染方案,放弃了 UE 自带的方案
- UE 自带方案的问题:
- 在材质(贴图)种类不多于 4 种还比较可控,如果进一步增加,需要同步增加权
重通道与采样数,会导致采样消耗与显存空间消耗的增加- 8 种材质的话,每个像素需要采样 18 次(基色+法线占用 16 次)
- UE 虽然做了优化,在运行时会只统计当前 component 使用的材质种类,但是一
方面会导致美术同学在制作的时候需要小心翼翼,另一方面也会影响画面的表
现,同时这种做法还会增加 drawcall 数
- 在材质(贴图)种类不多于 4 种还比较可控,如果进一步增加,需要同步增加权
Ghost Recon 的 MaterialID 方案
- 采样像素对应的周边四个顶点的 MaterialID,并基于 ID 采样对应的 Material
Texture 进行混合(单层)- 如果不做处理,做硬过渡的,会是 blocky 的,可以通过基于距离的线性混合
+noise 抖动的方式来实现更自然的过渡
- 如果不做处理,做硬过渡的,会是 blocky 的,可以通过基于距离的线性混合
DFM 方案
- 通过 553 的方式,设置了两个 5bit 的材质 ID(32 种)与 3bit 的权重,支持美术
同学实现更精细灵活的控制 - 没有使用周边四个顶点的混合方式,而是采用基于三角形的混合
- 因为是两层混合,所以不能直接用 gather 指令,这样的话,就需要逐顶点采
样,也就不必非要使用 quad 了,使用三角形采样的消耗相对会低一些- 不过会不会导致效果上的瑕疵呢?
- 为了进一步降低采样消耗,这里每一个顶点对应的两个 Material,也只会取
其中权重大的一个,相当于虽然记录了 6 个 material,但实际采样的时候只
取了 3 个- 那相当于每个顶点还是只记录了一层材质,前面因为不能混合导致的
blocky 的问题应该还依然存在吧?
- 那相当于每个顶点还是只记录了一层材质,前面因为不能混合导致的
- 因为是两层混合,所以不能直接用 gather 指令,这样的话,就需要逐顶点采
Texture Array 带来的问题是显存消耗高,这里的解决方案是运行时可进行贴图的
StreamIn/StreamOut 的 Adaptive Dynamic Texture Array
- 简单来说,就是 Texture Array 中只存当前位置需要用到的 Texture 数据,其他数
据不加载,之后当玩家移动到新的位置时,会对 Texture Array 进行更新,将不用
的置换出去,将需要的添加进来,从下面的截图来看,优化效果是很显著的
这里也介绍了 DFM 的材质规范
在 POI 区域,经过评估,推荐使用 8 种材质(当前所在位置,最大材质种类数)
在野外,可以适当放松标准,扩展到 12 层
通过 PCG 导出的数据可以拿到当前位置所属类型,同时通过可视化工具来明确各
个位置的数据是否合规
高品质材质混合算法
传统的混合有两种算法
传统高度混合的实现逻辑实现复杂,这里给了一套优化算法,蓝色参数允许美术同学
调整
但是高度混合在远景的表现不如线性混合,这里用了一个公式实现从高度混合到线性
混合的自然过渡
地形渲染方案:CDLOD+VT Renderer 方案
VT 算法:
- 主要思路是希望将此前的采样、混合提前做好,得到一张只需要采样一次就能够
直接使用的高清贴图 - 如果要得到覆盖全图的贴图,尺寸可能以 GB 来记,内存、显存都扛不住,为了
解决这个问题,决定将这个贴图做成按需加载的:- 在运行时生成需要的部分(通常是分帧实现)
- 也可以离线,不过代价是包体增加,实际上会考虑将低 mip 的贴图放在
离线生成,因为 mip 越低,包体尺寸占用越少,生成所需的时间却越多
- 通常是先生成最高精度的,之后通过四叉树合并的方式生成次高精度
的,并按照这种方式不断往上生成更低精度的,直到覆盖全图 - 在加载的时候只加载当前视野中需要的贴图,且根据距离的远近,加载的贴
图的 mip 层级也会有所不同
- 在运行时生成需要的部分(通常是分帧实现)
- 存储上述当前视野需要的贴图数据的贴图叫做虚拟纹理,即 VT,VT 中的每一块
都是一个 Page,原始的 VT 的 Page 尺寸是相同的,Recon Ghost(kachen)提出
了一种 Page 尺寸不同的 Adaptive VT 方案,简称 AVT
UE 原生 VT 方案的问题:
UE 会将地形拆分为一个个的 Proxy(基于 Grid,比如可能是 100m 一个 proxy),
这些 proxy 中会带有很多的冗余数据(比如植被),而地图尺寸较大的话,proxy
数目就会很多,从而导致很大的内存浪费,Proxy 的 Streaming 也会存在卡顿
每个 Proxy 都是一个 DrawCall,导致 DrawCall 数目很高
UE 将物理数据跟 Mesh 数据放在一起管理,造成项目自定义优化策略的困难:
- 服务器不需要 Mesh,只需要物理数据
- 客户端希望将地形跟其他物件的物理统一起来管理,而这里原生的设计是割
裂的 - Mesh 跟物理的加载距离会希望设计得不一样,这里也会存在困难
优化方案:
CDLOD+Heightmap Atlas+VTF,只用一到两个 DP 完成地形 Mesh 的绘制
- Heightmap Atlas 是为了合批方便,推测这个数据是在运行时构建的,类似于
VT,会为近景分配高分辨率,远景分配低分辨率(?) - VTF 则是为了在 VS 中基于 Heightmap 实现顶点的 transform 变化
- 可以针对不同的机型控制 Mesh 的面数与 Heightmap 的分辨率,实现可伸缩
品质提升
生态变化:
通过一张贴图存储颜色、湿度、明暗数据来实现场景不同位置效果的差异,提高
丰富度
每米一个像素,数据用 clipmap 存储,滚动更新,内存从 133M 到 2M,消耗就是
多了一张贴图采样与几个跟表现相关的 ALU 计算(染色等)
峭壁+河道(地表做了染色)
植被:可以存储颜色、AO、树木的年龄、健康度等数据,实现生态的丰富表现
VT Renderer 中的 MatID 也做成了 Clipmap 形式:
通过多张具有相同分辨率的 MatID 贴图来覆盖不同精度不同距离的地形 Mesh
MatID 的 Mip 层级怎么得到,通过取 Prominent 来吗?
可以有效降低所有区域使用同一分辨率 MatID 贴图导致的内存、显存消耗与高强度的 Streaming 消耗
峭壁效果
传统手游不会上峭壁效果,而是平缓的高度变化占大多数,这是因为峭壁效果会
导致贴图的拉伸看起来不真实,而峭壁更像是 3A 品质的特性,对于提升手游品质
有重大作用
贴图拉伸是因为 UV 拉伸了,而被采样的贴图没有被拉伸,比如原来 1m 间隔的两
个顶点覆盖 0~0.1 的 UV 范围,现在变成了 10m 间隔的两个顶点覆盖了 0~0.1 的
UV 范围,才会出现问题,如果将贴图做压缩,即将原来 0~1 范围的贴图压缩到
0~0.1 并 tiling,问题就解决了
上述思路跟 farcry 的 triplanar 思路是一致的,不过 farcry 的实现方案比较费,是
将峭壁的部分单独在 basepass 执行的,每帧都有消耗,而 DFM 的方案是将这个
计算 cache 到 VT 中,从而只需要执行一次即可
按照 triplanar 的实现,需要采样 XYZ 三个方向的数据并根据当前像素法线分量作为权
重来进行混合,消耗还是太高,这里尝试了多种方案,找到了一种兼顾表现与性能的
方案:
Lazy 方案:三个方向中,选择权重最大的方案,只有一次采样,但是会有硬边界
Bi-planar 方案:三个方向选择其中两个最大的做混合,过渡效果还可以,但是需
要两次采样
Stochastic 方案: 将权重作为概率,做随机采样(用 alpha blend 模拟 alpha test,类似 dithering),不贴着看其实是看不出问题的,适合手游
远景优化
手机版本面数远低于 PC,细节丢失严重
这里用到的 VT 包括 RVT 跟 SVT,两者共享同一个 Atlas:
RVT 覆盖 Mip0~Mip2,是运行时生成的,每个 Page 覆盖的区域相对较小,
在运行时生成压力不大
SVT 覆盖剩余的更低精度的 Mips,是离线生成的,覆盖范围大,不适合运行
时生成(跟我理解的 SVT 概念不一样,实际的 SVT 是可以只加载部分的,而
非整个 Page 加载)
这里在移动端实现高精度地形的方案就是使用法线贴图:
精度下降主要出现在一个相对较远的距离上,近景的 mesh 精度是可以保障
的
精度下降的原因是远景 mesh 的顶点密度下降了,在不做任何处理之前,我
们烘焙的 SVT 只包含混合后的基色跟法线(来自于贴图混合后的法线,未合
入顶点高度的法线)
基于上述两点考虑,可以将顶点因为高度图采样带来的起伏效果,通过法线
的方式烘焙到 SVT 的法线贴图中,来模拟高精度的 mesh(虽然没有真正的
mesh 的视差效果,但是质量肯定是有提升的)
这种方法没有额外的性能开销,且美术同学可以手动编辑此贴图来实现自定
义的效果
近景细节优化
通过 VT,可以用较低的消耗模拟 PC 端的大量贴花效果
通过借鉴 FarCry 的立体贴花效果,可以模拟 tessellation 的效果以得到更高精度的
地形细节
平面贴花不会改变其覆盖范围的地形 mesh
立体贴花可以用一个 mesh 来模拟,只不过绘制的方式还是按照贴花的方式
来
立体贴花四周是平整的,保证能跟地形无缝贴合,内部区域会随着视角的拉
近而出现起伏,远景则退化为普通的 2D 贴花(像不像 mesh 的 LOD?)
通过 dynamic instance+相对严格的立体贴花 LOD 策略,可以用较低的消耗
来提升场景的地形精度
性能优化
- 传统地形采样方法的矛盾:
如果在地形的每个像素上都进行近景 tiling 贴图跟远景 tiling 贴图的混合(远处用
一个更大的 tiling 因子),采样数就直接翻倍了
而如果我们近景跟远景都只做一次采样,中间过渡区域才做两次采样并混合,就
会导致 DP 翻倍
解决方案:利用 VT 天然分 DrawCall 绘制的特性,可以不用增加额外的性能消耗,实
现更低的采样数(即只在过渡区域做两次采样并混合)
VT 是一块一块的生成的,每次只实现一块区域的复合贴图的生成,所以天然就可
以在这个地方根据到相机的距离来控制是走单采样,还是混合采样
单采样跟混合采样,会需要用到不同的 shader 变体,这个的选择由 VT Renderer
来负责
更进一步优化:减少同时存在的材质数目
下图中的小地图描述的是以顶点为一个像素记录下来的材质使用数据,可以看
到,只使用一种材质的在整张地图中占 65%,三种的则只有 13%
不做优化的情况下,内存中常驻着三种材质 shader 变体,会存在内存的浪费
这里会在离线的时候烘焙出每个地块的需要用到的材质 shader 类型,并在运行时
根据需要进行启用,从而削减上面的浪费(效果咋样?)
- VT 绘制时的优化
传统 VT 绘制,需要输出三张贴图,用于存储如下信息:
- Albedo,三通道
- Normal,两通道
- Mask,单通道,存储个像素是否需要被 clip 的信息
- AO、Roughness,也都是单通道的
这里的优化方式是放弃存储透明度的 Opacity 通道,将 3 张 RT 变成两张,带来的
问题是,半透效果会丢失(地形的半透是用来干啥的?用于存储先绘制的贴花数
据的 alpha,后面用于减少 overdraw),而这是先绘制贴花后绘制地形必须的数据
为了解决上述问题,这里采用了 dither 的方式,用 alpha test 来模拟 alpha
blend,且这个过程是发生在贴图空间的,所以很难看出差异
MRT 的绘制,哪个数据写入哪个通道是 shader 控制的吧,为啥这里要用 subpass 来做
这个事情?
后面会说到,为了避免 overdraw,贴花绘制时序在地形绘制之前,所以绘制的过
程中需要用到并存储贴花的 alpha,但是最终输出到 VT 的时候,只需要保留两个
RT 即可
VT 绘制时的 overdraw 优化
问题:
每个(最高精度)page 只覆盖半米范围,如果有贴花的话,贴花中可能有很
大一部分像素会覆盖掉地形的渲染结果,这就导致被覆盖的像素的绘制的浪
费
场景中贴花跟道路数量多且密集,导致这个 overdraw 出现的概率很大
地形的材质非常复杂,overdraw 浪费的事件很多
方案:先绘制贴花,同时写深度,之后绘制地形,且当地形的像素深度距离相机
更近时才通过 depth test,从而避免 overdraw
这就是前面为啥需要保留 alpha 以及为啥需要用 mask 来模拟 alpha blend 的
原因了
总结
原文地址:[UFSH2023]揭秘《三角洲行动》如何在虚幻引擎4中实现先进的端手一体地形渲染方案 | 徐云翰 腾讯天美Y工作室
网友评论