“末日永恒”是“末日2016”的继任者。它是使用id Software的内部游戏引擎id Tech的第7次迭代开发的。Doom 2016在技术层面上给了我很大的启发,因为它的简约和优雅,同时仍然有很高的视觉质量。对于“永恒的毁灭”来说,这也没什么不同。毁灭永恒在很多方面都得到了改进,其中有几个值得研究,我将在这一帧分析中尝试涵盖这些方面。
这个框架分解的灵感来自于Adrian Courreges关于“末日2016”的研究。我相信这些图形研究让我们对AAA游戏中某些渲染问题是如何解决的有了很大的洞察力,并且很有教育意义。在这个细目中,我的目标是保持较高的水平,而不是深入了解每种渲染技术/过程。有些通行证可能不会在这里介绍,因为它们与Doom 2016非常相似,在Adrian Courreges的研究中已经很好地介绍了这些通行证。
我想在此强调,这些研究绝对是有教育意义的。我不以任何方式支持恶意目的的反向工程和窃取知识产权。如果你还没有玩过这个游戏,别担心剧透!我在这项研究中使用的部分是在游戏的开始阶段,它没有透露任何细节。
随着ID Tech7的推出,该引擎已经不再使用OpenGL,而是完全由Vulkan后端构建,允许他们更好地利用当前一代的GPU功能,特别是非绑定资源。
我们看的是比赛中接近开场的部分。这是一个内部,有几个敌人和很大一部分体积照明。就像它的前身一样,末日永恒使用的是前向渲染管道。Doom 2016主要是使用薄G缓冲区向前渲染的,用于屏幕空间反射。但是,这一次,所有内容都是完全向前渲染的,省略了G缓冲区。
在“愤怒”中使用了id Tech5的时候,引入了一个纹理流概念,叫做“Mega-Texture”,在“末日”的前一期中也用到了这个概念。该系统通过渲染包含哪些纹理数据是可见的信息的每一帧的所谓“反馈纹理”来工作,该纹理在下一帧被分析以确定哪些纹理将从磁盘流式输入。这有一个明显的缺陷,因为一旦纹理出现在屏幕上,加载它基本上已经太晚了,这会在它出现在屏幕上的最初几帧造成纹理模糊。在id Tech7中,id Software已经放弃了这种方法。
甚至在任何东西被绘制到纹理之前,发生的第一件事就是评估蒙皮。这通常在着色之前在顶点着色器中完成。这里使用的另一种方法是预先在计算着色器中执行蒙皮,该着色器将蒙皮顶点写出到缓冲区。这有几个优点,主要是不必在每个几何体过程的顶点着色器中进行蒙皮。这会导致较少的着色器排列,因为顶点着色器不必了解蒙皮。
计算着色器中的蒙皮与顶点着色器中的蒙皮没有太大不同,只是输出将写入中间缓冲区,然后可以在顶点着色器中使用,该顶点着色器可以将其视为常规静态网格。就像在顶点着色器中一样,对于每个顶点,计算着色器线程检索影响顶点的每个骨骼的变换,使用每个骨骼变换变换其位置,并基于存储在顶点上的蒙皮权重将这些位置相加。
János Turánszki写了一篇关于如何使用计算着色器实现它的精彩文章:https://wickedengine.net/2017/09/09/skinning-in-compute-shader/.。
这里另一件值得注意的事情是Doom Eternal中Alembic缓存的使用。这些缓存包含在运行时流式和解压缩的烘焙动画。正如Digital Foundry在他们的技术分解中所描述的那样,它被用于从大型电影片段到地板上的小触手的广泛的动画。这对于使用蒙皮动画(如有机物和布料模拟)难以实现的动画特别有用。您可以将Alembic缓存与可回放且高度压缩的视频进行比较。如果你有兴趣了解更多,我建议你看看Axel Gneting在Siggraph 2014上的演讲。
接下来是阴影渲染。与它的前身相比,id Tech7中阴影贴图的处理方式似乎没有什么大的变化。
如下所示,阴影在大的4096x8196px 24位深度纹理中渲染,该纹理可能会因质量级别的不同而有所不同。纹理在帧之间是持久的,并且如Siggraph 2016的“魔鬼在细节中”中所述,阴影贴图中的静态几何体被缓存,以避免在每一帧重新绘制阴影贴图。技术相当简单:只要灯光视图中没有任何移动,就不需要更新阴影。如果平截体中的动态对象移动,则会将“缓存”阴影贴图复制到实际阴影贴图中,并在顶部重新绘制动态几何体。该缓存的阴影贴图是相同的阴影贴图,但仅具有静态几何体,因为您可以假设它们永远不会更改。这样就不必在每次需要更新时都在锥体中绘制整个场景。当然,当灯光移动时,整个场景必须从头开始重新绘制。
在照明期间对阴影贴图进行采样时,使用3x3 PCF采样方法来平滑阴影边缘。对于太阳光,级联阴影贴图用于更好地分布质量,因为它覆盖了环境的很大一部分。
下面是阴影地图集的近距离观察。重要性较高、屏幕面积较大或距离相机较近的灯光将获得更大的地图集部分,以获得更好的分辨率。这些启发式算法是动态评估的。
不透明的几何体从玩家的枪开始渲染到仅有深度的目标,然后是静态几何体,最后是动态几何体。深度预程通常用于避免以后沿几何体重叠的管道进行不必要的像素着色器计算。深度预程在正向渲染器中尤其重要,在该渲染器中,由于像素透支而导致冗余像素计算极其浪费。使用深度预过程,实际的前向照明像素着色器可以在执行之前通过与深度缓冲区进行比较来拒绝像素,从而节省大量性能。
除了渲染深度之外,预渲染还会渲染到另一个颜色目标。对于动态几何体,速度是使用运动向量渲染的,运动向量是从上一帧中的像素位置减去当前位置的位置。我们只需要X和Y轴上的运动,因此运动存储在16位浮点渲染目标的红色和绿色通道中。该信息稍后用于后期处理,以应用运动模糊和重新投影来进行时间抗锯齿。下面的图像被夸大了,因为这张快照没有太多运动。静态几何体不需要运动向量,因为它们的运动可以从摄影机运动中派生出来,因为它们只是相对于摄影机“移动”。
接下来,生成深度缓冲器的分层MIP链,其类似于MIP图,但不是取4个相邻像素的平均值,而是取最大值。这通常在图形中用于各种目的,如加速屏幕空间反射和遮挡剔除。在这种情况下,此MIP链用于加速稍后介绍的灯光和贴花剔除。最近,通过一次写入多个MIPS,MIP生成在一次遍历中完成。在“永恒的毁灭”中,它仍然按照传统分别为每个MIP进行派单。
到目前为止,与Doom 2016相比,并没有太多明显的变化。然而,“网格贴花”是对“末日永恒”中引入的网格渲染管道的补充。与在环境中自由放置的常见贴花工作流不同,网格贴花是由艺术家在网格创作管道期间放置的,因此属于网格。以前,末日严重依赖贴花,并在这款游戏中增加了所谓的“网格贴花”,以获得更好的细节和灵活性。“网状贴花”是指小贴花,如螺栓、格栅、凸起、贴纸、…。就像传统的贴花一样,它可以修改底层表面的任何属性,如法线、粗糙度、基色、…。
为此,以下几何体过程将每个贴花的ID渲染到8位渲染目标中。稍后在着色期间,将对此纹理进行采样以检索ID,该ID用于检索与每个绘制调用绑定的投影矩阵。矩阵将像素的位置从世界空间投影到纹理空间。然后使用这些坐标对贴花进行采样,并与底层材质混合。这是非常快的,让艺术家们可以疯狂地使用大量的贴花。由于ID渲染为8位纹理,因此每个网格的最大贴花数量理论上为255。
其中一个要求是在绘制网格时将所有贴花绑定到管道。Deom Eternal使用完全无绑定的渲染管道,允许它们一次绑定所有贴花纹理,并在着色器中动态索引它们。稍后将更多地介绍这个无绑定管道,因为这对于他们在这个游戏中做过的其他技巧是很重要的。
“末日永恒”中的灯光都是动态的,一次可以有数百个灯光出现在一个视图中。除此之外,正如前面提到的,贴花在游戏中非常重要。在Doom 2016年,情况已经是这样,贴花数量可能高达数千个。这就需要一种真正可靠的剔除方法,以避免稍后着色期间的大量像素着色器成本。
在Doom 2016中,集群灯光剔除的CPU变体被用来将灯光和贴花桶到锥体形状的“工作服”中,然后在着色过程中通过从像素的位置确定集群索引来读取这些工作服。每个簇的屏幕大小为256px,24个切片对数分隔,以确保方形。这已经成为一种常见的方法,类似的方法也被用在许多游戏中,比如底特律:成为人类和正义的事业。
考虑到在“毁灭永恒”中动态灯光(数百个)和贴花(数千个)的增加,CPU群集灯光剔除是不够的,因为体素太粗糙了。对于id Tech7,设计了一种不同的方法。他们使用在不同阶段执行的计算着色器创建了一个软件光栅化程序。首先,贴花和灯光由六面体(有6个面的形状)捆绑在一起,并发送到计算机光栅化器管道。这会将顶点投影到屏幕空间中。接下来,第二个计算着色器根据屏幕边界剪裁三角形,并将它们分成256x256px的屏幕平铺。同时,灯光和贴花实体就像在传统的群集剔除方法中一样被写入Flaxels。下面的计算着色器执行类似的工作,但使用的是较小的32x32px大小的平铺。在每个平铺中,通过深度测试的实体在位域中被标记。最后一个计算着色器将这些位场解析为最终由照明过程使用的灯光列表。有趣的是,像传统的集群方法一样,实体索引仍然被写入256x256px大小的3D‘froxels’。在存在大深度不连续的情况下,使用新灯光列表和群集灯光列表两者的最小计数来确定每个瓦片中的灯光数量。
以上是对该过程的非常简明的解释,如果您不是非常熟悉传统的光栅化,那么它可能没有多大意义。如果你想更好地理解这一点,我建议你研究一下这个过程是如何运作的。ScratchaPixel很好地解释了这一过程是如何工作的。
除了灯光和贴花,所谓的“可见性框”也使用该系统进行筛选,用于游戏可见性查询。因为软件光栅化对于计算线程来说是一个很长的过程,所以占用率可能很低,并且添加这些额外的可见性框几乎不需要额外的成本。考虑到这一点,轻筛选很可能是异步进行的,因此对性能的净影响非常小。
环境光遮挡以相当标准的方式以一半分辨率计算。从半球中每个像素的位置发射16条随机射线。使用深度缓冲区可以确定与几何体相交的光线。这些光线击中几何体的次数越多,遮挡程度就越高。这里使用的遮挡技术被称为“屏幕空间定向遮挡”(SSDO),尤里·奥唐奈(Yuriy O‘Donnell)很好地描述了这一点。传统上,不是将遮挡值存储在单通道纹理中,而是将定向遮挡存储在3个组件纹理中。可以使用像素的世界法线的点积来评估最终的遮挡。
因为效果是以一半分辨率计算的,所以结果相当嘈杂。使用深度缓冲区应用双侧模糊以提高质量。环境光遮挡通常是相当低的频率,所以它不是很明显的模糊。
这是许多东西汇聚在一起的关口。与Doom 2016相比,这里的一切都是完全向前渲染的,只需几个巨大的超级着色器。据推测,整个游戏大约有500个流水线状态和12个描述符布局。首先渲染第一人称枪,然后渲染动态对象,然后渲染静态对象。请注意,顺序并不重要,因为我们已经有一个来自Depth PrePass的深度缓冲区,它可以提前拒绝与深度缓冲区中的深度不匹配的像素。
许多静态几何体和角色都有公共着色器。这就是id Tech与任何其他AAA游戏截然不同的地方。许多AAA游戏引擎都有着色器图形和静态着色器功能,这使得内容创建者可以非常有创意,疯狂地使用不同种类的材料。所有这些不同的材质都会生成自己独特的着色器。结果是可能功能的所有组合的着色器排列爆炸。相反,在id Tech中,几乎所有的材质和功能都被组合成极少的大型“超级着色器”。这允许图形管道更积极地合并几何图形,从而显著提高性能。我将在下面回到这个问题上。
一个有趣的观察是,整个图形管道都包含了“无绑定资源”的概念。这意味着代替绑定例如漫反射、镜面反射、粗糙度,…。纹理在每次绘制调用之前,场景中的整个纹理列表一次绑定。通过使用统一/常量传递到着色器的索引,可以在着色器中动态访问列表中的纹理。这意味着任何纹理都可以从任何绘制调用访问,并允许进行许多有趣的优化,下面我将描述其中之一。
在完全无绑定的资源体系结构之上,所有几何数据都是从单个大型缓冲区/池中分配的。所有几何体在此缓冲区中只有一个偏移量。
这些东西汇聚在一起的地方,是id Tech7中最有趣的技术:动态绘制调用合并(Dynamic Draw Call Merging)。这依赖于无绑定架构和统一顶点内存。在进行任何渲染之前,计算着色器会动态创建新的“间接”索引缓冲区,以有效地将来自不相关网格的几何体合并到单个间接绘制调用中。如果没有绑定资源,这将永远无法实现,因为此绘制调用合并可以跨不共享相同材质属性的几何体工作。像这样合并几何图形可以极大地减少绘制调用和CPU提交时间。动态索引缓冲区可以在深度预过程和照明过程中重复使用。
在计算着色器中,最常见的屏幕空间反射是通过从世界空间中的像素沿取决于反射曲面粗糙度的反射方向光线行进来完成的。这在Doom 2016中也没有什么不同,它写了一个薄的G缓冲区作为向前传球的一部分。然而,在“永恒的毁灭”中,这个G缓冲区不再被写入。这里的超级着色器的奇妙之处在于,即使是屏幕空间反射也是在向前着色器中直接计算的,而不是在计算着色器中单独计算。我很想了解像素着色器中的这种差异对性能的影响。他们似乎试图以更高的寄存器压力为代价,通过减少渲染目标的数量来减少内存带宽。
当所需信息在纹理中不可用时,使用屏幕空间纹理完成的效果通常会受到视觉瑕疵的影响。这在屏幕空间反射中尤其明显,在屏幕空间反射中,不可见的反射对象无法反射。这个问题是使用静态镜面反射立方体贴图作为后备方案来解决的,这也是一种非常传统的方法。
由于末日永恒不再使用Mega-Texture,反馈纹理也不再创建。
在《毁灭边缘》中,GPU粒子模拟的一部分是在计算着色器上完成的。某些粒子系统依赖于屏幕空间信息(如深度缓冲区)来执行碰撞等操作。这些模拟需要在深度预通过之后运行,其他粒子系统可以在帧中尽可能早地运行,并且通常可以在异步计算上运行。有趣的是,与传统的计算着色器粒子模拟不同,模拟是通过执行在计算着色器的命令缓冲区中定义的一系列“命令”来进行的。每个计算着色器线程迭代每个命令,每个命令都可以包含几个命令,如“kill”、“emit”或修改粒子的参数。它就像是在着色器中编写的字节码机器。我不太明白这是如何工作的,但这是基于布兰登·惠特利在Siggraph 2017上的“命运粒子架构”,它使用了非常类似的方法,并在许多其他游戏中使用。我相信这也类似于虚幻引擎4中尼亚加拉粒子系统的工作原理。
就像在Deom 2016中以及在2016 Siggraph Talk中所描述的那样,粒子照明分辨率与实际屏幕分辨率是分离的。这使得他们可以根据质量、屏幕大小和手动控制来控制每个粒子系统的分辨率。对于低频效果,照明通常可以在低得多的分辨率下进行,与需要更高分辨率的火花相比,没有明显的差异。照明和主光方向存储在2个2048x2048px地图集中,这些地图集在所有过程中都可以访问,就像使用非绑定资源的表中的任何其他纹理一样。随后,将绘制简单的几何体,对该粒子贴图集进行采样以渲染粒子。
本节介绍体积照明。这包括4个过程,首先通过光线行进穿过天空并朝向光线,为天空的大气生成3D LUT纹理。
从图像中可能不能立即理解这种纹理所代表的含义,但如果我们将其旋转90度并水平拉伸,就会清楚地看到这代表了大气的散射。因为垂直方向的方差/频率比水平方向多,所以分配的垂直分辨率更多。大气被表示为球体,因此水平旋转通常称为“经度”,垂直旋转通常称为“纬度”。大气散射是在覆盖360度经度和180度纬度的半球中计算的,球体的上半部分。LUT有32个深度切片,以覆盖与查看器的不同距离。计算成本在32帧内摊销,而不是每帧重新计算天空数据。
使用此LUT,下一遍
.