前言
这周继续更新读技术文章。在GDC的文档库中我有一个核心感兴趣的点就是很多主机大厂如何迈进PS5(以及XBOX-X)这个世代。之前也读到过《漫威蜘蛛侠》的续作在巨量建筑LOD方面的进化,这次带大家一起来看看新《战神》这个系列在画面迈入当时的“次世代”的一些调整。
原文档的分享人是2020年才加入圣莫妮卡工作室(缩写SMS)作为渲染主程的Stephen McAuley(有趣的是他之前一直供职于育碧蒙特利尔,主要参与Far Cry系列)。
分享人介绍
这篇分享整体是一个产品向的讲座,各部分间的信息密度不太一样。各节分别是:项目背景、游戏中的叙事元素(及其美术方案)、美术工作流程的重整、PS5上的画面进化、团队自我提升、性能优化。除开本文将翻译和分析的第三部分,其它部分更多是项目管理及产品规划的视角——例如提到了使用JIRA作为核心的敏捷反馈项目管理工具,以及他个人和团队与之前的一些Leader间的一些配合细节。
而至于要重整美术工作流程,也并不是因为他是空降的而需要“新官上任三把火”,而是前作的光照模型整体还是基于“各场景手调光强度”的古老方案来进行的,或许作为线性流程的有明确室内外切换的场景编辑来说勉强够用,但确实要做更细致(一天更多时段)更写实(更多动态光源)的光照已经不足了。这之中他们对于光照、反射和折射几方面都有美术制作规范和渲染管线上的调整,但我个人还是更聚焦在PS5的画面进化这个技术向的方向。
本文整体还是以翻译为主,最后一节会补充对于SSDO的简单介绍。打星号的部分则是我个人对原文的补充(文中很多基础概念就不一一解释了,之前我有很多篇文章介绍过,比如BVH)。
1 PS5上的专属特性概览
不同机型画面模式一览
*这里面HFR是高刷新率,VRR是可变刷新率。
PS5平台专属画面特性
——性能模式:
- 曲面细分
- 屏幕空间基于方向的遮蔽
- 简单的邻接硬阴影
- 强化的屏幕空间反射
- PS5特定的模型和光照
——画质模式:
- 基于机器学习的纹理超采样(文末附链接)
- CubeMap射线追踪
- 进阶的邻接硬阴影
- 强化的天空光照、景深、曲面细分、屏幕空间反射
- 更高分辨率的阴影
- 更高质量的LOD
*如果渲染向的文章看过一些,或许会感觉工业化的引擎技术就像一个从“模块方案超市”从选配的过程——某种意义上也确实如此,例如反射就可能有“镜像摄像机拍摄”“静态CubeMap采样”“屏幕空间反射(缩写SSR)”“光线追踪”等很多方案;阴影之前介绍过的一些方案还主要是基于静态烘焙和少量实时光源的Shadow Map的,而要实现动态的邻接阴影甚至都要弄出胶囊体AO这样的近似方案了,而相对来说“邻接硬阴影”则是质量和精度相对更高的一种阴影方案。
2 基于CubeMap的射线追踪
- 《战神:诸神黄昏》使用视差校正的CubeMap技术(文末附资料链接)
- 需要找出反射线与一个物体包围盒的交点
- 用于调整cubemap采样的位置(视差校正)
图中演示了需要视差校正的一种情况
- 通过反射线与场景物体几何体求交以进一步提升——使用硬件光线追踪
- 与屏幕空间的射线追踪反射技术结合——在可获得的点采用屏幕空间(反射)数据
- 屏幕空间射线追踪——命中位置、颜色
- 如果命中一个点:射线追踪其BVH(一种空间加速结构、之前有文章介绍过);如果命中位置能对应到一个屏幕空间的位置,从屏幕空间缓冲区中采样数据(屏幕内);否则从cubemap中采样数据(屏幕外)
- 混合屏幕空间反射(SSR)和BVH检测的结果
*之前设想过这种做法来补充屏幕空间反射追踪不到的问题,不过实际读到3A中这么做这还是第一篇。
CubeMap射线追踪关闭时
CubeMap射线追踪开启时
*上面2图是关闭与开启这项技术时的对比——可以看到关闭时的反射其实完全是“不真实”的,仅能提供一种氛围。
分层DEBUG展示
- 蓝绿色——屏幕空间命中,屏幕空间采样
- 紫色——BVH命中,屏幕空间采样(位置校正屏幕内)
- 黄色——BVH命中,cubemap采样(位置校正屏幕外)
- 我们并不射线追踪实际的游戏几何体
- 作为替代,我们创建了代理几何体:从GI烘焙的过程中取GI点元数据;通过Houdini转换程网格
图中是他们开发的点元预览工具——位置、表面颜色、法线
*中间有几页是这个工具基于点元数据生成网格的过程,有兴趣可以去看看原文档,我就不放步骤图在这里了。
最终生成的网格
下面摘翻一下整个网格生成的步骤:
- 使用球和中心算法来从点云生成网格(open3d python库有一个便利的封装)。如果数据是非常松散的情况容易得到“假阳性”(错判)的结果,这时生成的几何体会有很多问题。
- 通过生成VDB来清理球和中心算法的结果,并光栅化至几何体。(“VDB”, so named because it is a Volumetric, Dynamic grid that shares several characteristics with B+trees。简单来理解就是一种人为定义体积的分层加速结构——文末会附链接)
- VDB光栅化结果是双面的,通过片元位置的线路追踪来确认正面并移除背面(算法线和目标位置的夹角)。
- 通过Houdini中的减面工具,在保持曲率的基础上大幅消减网格数——原始目标是每一团集群中33K个三角形。这一限制在光线追踪的性能提升后会有所提高。
这一技术的性能总览
- 由于第一步的几个技术都需要从屏幕空间缓冲区中读数据,因此只读一遍以用于采样,避免重复采样。
- Ray binning可以理解成一种预筛选过程,处理64X32的格子性能开销也很小。其它步骤都是前面介绍过的步骤。
- 图中展示的是PS5上4K分辨率的性能情况。
3 邻接阴影的新方案
- 阴影分辨率和阴影偏移量带来了2018版《战神》中的很多阴影方面的主要烦恼
- 大的阴影偏移值会导致角色面部(等精细部位)缺乏阴影
- 这里(后面)展示了一个“漏光”的例子
- 邻接硬阴影是这类问题的解决方案
*阴影偏移量:是shadow map方案中做采样精度时的一个容差值,因为shadow map的主要方案例如CSM其实都还是基于
比较深度缓冲,在屏幕像素、目标点位置、shadow map的深度值之间有一定精度误差。常规来说就是通过引入一个bias来做容差,但也会带来例如“阴影范围错误地变小”之类的问题。
在2018版中,由于阴影精度不够,鼻子上是处于“漏光”状态的
- 通过射线追踪解决邻接硬阴影——朝向区域光源做射线追踪
- 思路:在shadow map空间中做射线追踪。类似屏幕空间的其它射线追踪技术——没有采用硬件光追加速。
*后面一段会详细逐步介绍这个算法(in practice):
Let’s consider how we shade a pixel. We want to trace rays to this area light source, through this depth buffer which is represented as a set of green lines – each line segment is a depth buffer texel with a certain depth. It’s important to remember that this depth buffer really doesn’t represent a contiguous height field, we have to treat it as an isolated collection of fragments.
考虑我们是如何着色一个像素的——我们想要射线追踪区域光源,透过图中标绿线的深度缓冲(每一段代表一个特定深度的缓冲区中的图素)。重要的是需要记住深度缓冲并不代表一片连续的深度区域,我们必须把其中每个片元视为是独立的。
We’re ray tracing to see if we intersect the depth buffer. In this case, let’s imagine we trace the simplest ray, one aligned with the direction of the light. In this case you can see that the first three samples are occluded, and the fourth sample is not occluded. As we’ve changed from shadowed to non-shadowed, we detect that we’ve hit an object, stop ray marching, and take the shadowed sample.
我们进行射线追踪来检查是否与深度缓冲相交。这次我们假设仅发出一条简单的射线,它与光源的方向一致。你能看到最初的3个样本是被遮挡的,而第四个样本是未被遮挡的。由于样本已经从阴影中变为无阴影的,我们探测出已经命中一个物体,结束光线步进的过程,并得出阴影样本。
This all seems rather naïve though – we might as well have just taken the original shadowed sample. However, other rays that aren’t parallel to the direction of the light are more complex.
虽然所有这些看起来都很不成熟——无妨,至少我们可以先取得最初的阴影样本。然而,其它和光源方向不平行的射线情况会更复杂。
*这里朝光源的深度缓冲做追踪其实就是在光源对应的shadow map上做追踪。
Let’s trace another ray. After a few steps we notice we are no longer shadowed. Does this mean that we’ve intersected an object? Well, clearly it doesn’t in this case.
让我们追踪另一条射线。经过几个步骤后我们发现不处于阴影中(标白色的部分),但这是否意味着前面的步骤我们与一个物体相交了(从红变白)?显然图中的情况就没有相交。
We also sample on the disk at the original pixel’s depth, and discover that sample isn’t shadowed either. The fact that the two samples are in agreement (they’re both non-shadowed) shows that no boundary has been crossed.
我们也在原始像素的深度缓冲中进行采样(即摄像机位置的深度缓冲,而阴影是光源位置的深度缓冲),并发现样本在其中也不在阴影中(被遮挡)。两个样本一致(都不在阴影中)的事实显示没有边界被跨越。
So we continue ray tracing until we hit the light…
所以我们继续射线追踪直到命中光源。
The samples on the disk vs the samples on the ray are always in agreement, showing we never crossed a boundary. This means we take the last result and say that the ray isn’t shadowed.
像素盘(屏幕空间像素的缓冲区数据)和射线中对应的采样是始终一致的,显示我们没有跨越边界。这意味着最终的结果是这条射线没有被遮挡。
Let’s trace yet another ray, a more complex one this time. We trace until we find a disagreement between the disk and the ray samples, which means a boundary has been crossed… but has it?
让我们追踪另一条射线——一个更复杂的情况。我们追踪直到发现两个缓冲区中采样点的遮挡状态是不一致的,这是否意味着跨越了边界呢?
This is an innate problem of screen space ray tracing techniques. We don’t know if those individual depth texels are connected or not (or if they’re not, how “thick” each individual texel is).
这是屏幕空间射线追踪技术的一类天生的(会导致错判的)问题。我们不知道那些有着独立深度值的像素块是否是连续的(以及如果不连续,每个独立的像素块有多“厚”)。
However, for our purposes, we say the depth buffer is contiguous. This prevents light leaking and worst case, will just give us the same result as regular PCF filtering.
然而,基于主要的目的我们把深度缓冲视为是连续的。这能避免漏光等最坏的情况,最差的情况也会和PCF方式下的阴影一致。(PCF简单说是一种通过邻接采样shadow map来进行提高精度和做软阴影的方案)
So in the case of this ray, we determine we’ve crossed a boundary, and take the result of the sample before that. This ray is shadowed.
所以对于图中情况的射线,我们认定它跨越了边界,并选取最终位置的前一个采样点作为结果。这个射线被视为是被遮挡的。
We trace each ray a sample at a time, killing a ray whenever it intersects the depth buffer
(实际运行时)我们同时发出这些射线,并中止那些与深度缓冲相交的射线(得出采样点的交点位置)。
*整个过程是基于光线步进ray marching方案的,因此他们也没有采用硬件加速的光线追踪。
我们通过以下方式进一步提高阴影质量:
- 抖动阴影采样位置(多帧基于时间的混合)
- 在采样shadow map时使用过滤器:并不是每个像素都是二元的输入、输出过程;意味着可以使用更少的射线以提升性能(可降低原始采样精度)。
PCF阴影
邻接硬阴影
*图中对比了2种方案的画质差距。可以看到手指和弓弦部分有明显差距。
- 射线追踪相对比较慢——可能话尽量提前开始运算
- 创建一个向下采样的限制最大最小值的shadow map:基于阴影过滤的半径进行扩张;类似Abadie2018的景深技术方案。
- 使用动态扩张的最大最小深度:如果全部在或不在阴影中则可以提前进行运算;把光追运算更紧密的限制在需要的区域内。
——我们的邻接硬阴影并不是“物理原理的”(主要指参数化上)
- 这一特性是后期开发的
- 光照艺术家已经手动调整过阴影过滤的半径
- 最终能尽量保持和之前手调的(PCF方式)显示一致
——射线追踪的“区域光源”被假设为:
- 阴影过滤半径的值
- 着色像素对应的2米范围
*这里之所谓是打引号的“区域光源”,主要是因为他们的目的是提高阴影质量,因此虽然采样了一定面积的阴影遮蔽情况,却不一定是朝真实的区域光源做的采样(有可能是在主光源或点光源的shadow map上也做类似处理)。
4 漏光的解决与SSDO简单介绍
——总的来说,我们发现移除漏光问题是最能带来视觉提升的方面
- 这通常出现在本身不该被照亮的物体部分被错误照亮的情况,表现为光好像“漏”了(实际是采样精度或信息不足)
- 之前介绍的两种方案都对减少漏光情况有帮助
——然而,把SSAO升级成SSDO带来了最大的影响;添加了SSGI(屏幕空间的全局光照)也有帮助
*SSAO和HBAO之前有文章介绍过,这里直接从SSDO开始介绍。至于SSGI,也并不如其概念上的大词那样是一项太复杂的技术,简单说它能在极有限的情况下通过屏幕空间深度信息做光线步进追踪计算出间接光照。篇幅原因就不介绍了,有兴趣可以网上搜搜。
左侧是SSAO,右侧是SSDO
*图中引用了Games202的课件图。可以看到SSDO除了能得到和SSAO(或HBAO)相同的AO范围外,它还能在屏幕空间利用深度缓冲来追踪间接光照信息,所以能得到如右边展示的有间接光照(光弹射一次)颜色的AO效果。
*SSDO的核心步骤可以概括如下:
- 在目标像素法线所在的半球(HBAO方式,需要GBuffer法线信息)选取一些采样点
- 检测采样点是否在表面之上
- 对在表面之上的采样点进一步做一次弹射的间接光照计算(间接光可以来自周围物体或cubemap),之后对光照贡献求和
SSDO间接光照计算的图示
*具体的射线追踪方式很类似前面“邻接硬阴影”一节,都是一种屏幕空间的ray marching技术。
*这一方案的局限如下:
- 只能近似计算小范围的全局光照信息
- 对于采样范围内的微小结构可能会有误判
- 不能追踪超出屏幕空间范围的信息
最后是一些图片对照:
SSDO和SSGI都关闭时
SSDO开启时
You can see better shadowing behind Freya’s sword hilt, plus where Kratos’ arm shadows Atreus.
你可以看到芙蕾雅剑柄有更好的阴影,以及奎托斯的的胳膊正确遮蔽了阿特柔斯。
*但说实话由于本身精度就很高的原因,这个例子不仔细看确实不明显。
SSDO和SSGI都开启时
结语
其实文中的的各处“射线追踪”方案都是广义的“光线追踪”或“路径追踪”的组件,有了硬件上对光追的加速后,确实一定程度上能把更多的组件纳入渲染特性中。但实际上即使在PS5,在《战神:诸神黄昏》中,谈到“光追”其实支持的特性还只是一小部分——更多的射线以及更多的采样数其实都不太可能。整篇文章中提到的射线追踪技术,除了追踪BVH之外其它都还是屏幕空间的技术;对比Nvidia话语体系下的path tracing整体方案(更不用说AI超采样方面的技术),其实相对是挺寒酸的。
如果不看这篇文章,我可能对于2018年的年度游戏《战神》的渲染技术选型相对的古旧没有太多概念——可能它的更多精细度都表现在了模型精度上,因此更需要配合定制化的关卡场景;同时他们还有一个负担就是旧版的PS4。确实在当时看来《神秘海域4》或《战神》都是“真”的不能再“真”的游戏了,但其实这种也都是巧妙藏拙的结果——最大的优化就是设计出不需要优化的应用场景,因此需要各种限制自由度。
实际上《战神:诸神黄昏》尽管在前作基础上画质进化很多,但是由于隔了多年记忆不准确了,实际玩上确实没有画质明显变好的感觉。而读了这篇分享,我发现PS5上第一方的光追应用也还是捆手捆脚的,也不得不感叹——追求极致画面这件事或许已经越来越不能带动3A游戏领域向上增长了。
例如大部分游戏的室内外光源切换,其实是经不起推敲的,但这么多年玩家也这么接受下来了;而进一步把这事弄得更“真”要付出的代价暂时是无从谈起的,只能等将来算力进一步跃升的一天,或许就能自然容纳这件事了。
不过我也不同意一种言论,就是“把影子弄那么真有意义么,我阴影选项都开低的,帧数最重要”;对个人来说这么选无可厚非,但开发商追求画质极限是游戏产业生态很重要的组成部分。事实上无论哪个3D引擎,画面质量的高低不就是通过表面材质着色质量、反射质量、阴影质量、体积光照质量等等一系列细节撑起来的么,而3A游戏天生就是为发挥当时主机的最高技能而生的,这一点无可厚非——画面最好的游戏显得不好玩,那不是画面的错,是其它方面出了问题。
最后是一些资料链接:
Rendering-God-of-War-Ragnarok 的GDC地址
介绍VDB的一篇知乎文章
介绍Parallax Corrected Cubemap的一篇SIG