DOOM(2016) – 图形研究

DOOM(2016) – 图形研究

原文地址

DOOM在1993年开创了游戏设计和力学的根本变革,这是一个世界性的现象,推动了像约翰·卡马克(John Carmack)约翰·罗梅罗(John Romero)这样的标志性人物。

23年后,id Software现在属于Zenimax,所有的原创创始人都已经走了,但并没有通过提供一个伟大的游戏来阻止该队的所有人才。
新的DOOM是专利权的完美补充,使用新的ID Tech 6引擎,前Crytek Tiago Sousa在约翰·卡马克(John Carmack)离职后,现在担任铅渲染程序员的角色。
历史上,软件知道几年后开源引擎,这通常会导致很好的重制故障。id Tech 6是否符合要求,但仍然有待观察,但我们并不一定需要源代码来了解在引擎中实现的漂亮图形技术。

框架如何呈现

我们将在下面的场景中检查玩家攻击由一些拥有的敌人所保护的戈尔巢的场景,在比赛开始之后获得Praetor Suit

与现在发布的大多数Windows游戏不同,DOOM并不使用Direct3D,但提供了一个OpenGLVulkan后端。
Vulkan是新的热点,Baldur Karlsson最近在RenderDoc中增加了支持,很难抵抗DOOM内部的挑选。下面的意见基础上,游戏运行福尔康在GTX 980上的所有设置,有些是别人都取自猜测Siggraph大会上展示由亚哥·索萨和吉恩·格弗罗伊

使用doom4的这个snap 关卡编辑器来进行对于这个doom4的编辑器来进行处理了

然后在doom4的过程中其实还是有很多的问题的。

对于材质的编辑器,

还有这个对于很多的 电影的编辑,

对于这个几何体还有很多的网格模型等等加工;了

先进行裁剪到一定的地步,

使用ul

 

对于光照的三种灯光,区域光,点光源,

doom4使用了很多过去的老旧技术

对于加载megetexure这个来进行对于材质文件的加载了

巨型纹理更新

第一步是Mega-Texture更新,这是一种已经存在于RAGE中使用的ID Tech 5中的技术,现在也在DOOM中使用。 要给出一个非常基本的解释,这个想法是在GPU内存上分配了几个巨大的纹理(16K x 8k的DOOM),每个纹理都是128×128个拼贴的集合。

16k x 8k存储,128 x 128页

所有这些瓷砖都应该在好的mipmap级别代表理想的实际纹理集合,这将在像素着色器后来渲染您正在查看的特定场景。
当像素着色器从“虚拟纹理”读取时,它只能从这些128×128的某些物理图块中读取。
当然这取决于玩家看的地方,这个设置会改变:新的模型会出现在屏幕上,引用其他虚拟纹理,新的图块必须被流式传输,旧的流将被流出…
所以在一帧的开始, DOOM更新了一些瓦片vkCmdCopyBufferToImage,将一些实际的纹理数据带入GPU内存。

关于巨型纹理更多的信息在这里这里

阴影地图集

对于每个光投射一个阴影,生成一个独特的深度图,并将其保存到巨大的8k x 8k 纹理图集的一个瓷砖中。然而,并不是每个帧都计算出每一个深度图:DOOM大量地重新使用前一帧的结果,并只重新生成需要更新的深度图。

8k x 8k深度缓冲区
(上一帧)
8k x 8k深度缓冲器
(当前帧)

当光是静态的,并且仅在静态对象上投射阴影时,只需简单地将其深度图保持原样,而不是进行不必要的重新计算是有意义的。如果一些敌人在光线下移动,则深度图必须再次生成。
深度图尺寸可以根据与相机的光距离而变化,重新生成的深度图也不一定保持在图集内的同一瓦片内。
DOOM具有特定的优化,例如缓存深度图的静态部分,仅计算动态网格投影并合成结果。

深度预通

现在渲染所有不透明的网格,仅将其深度信息输出到深度图中。首先是玩家的武器,然后是静态几何,最后是动态几何。

深度图:进度20%
深度图:进度40%
深度图:进度60%
深度图:进度80%
深度图:进度100%

但实际上深度并不是在深度预通过期间输出的唯一信息。
虽然动态对象(拥有的,电缆,玩家的武器)被渲染到深度图,但是它们每像素的速度也被计算并写入另一个缓冲区以创建速度图。这通过在顶点着色器中计算前一帧和当前帧之间的每个顶点的位置差来完成。

速度图

我们只需要2个通道来存储速度:红色是沿着水平轴的速度,沿着垂直轴是绿色的。
群魔迅速向球员(绿色)移动,而武器几乎没有移动(黑色)。
黄色区域(红色和绿色等于1)?它实际上是缓冲区的原始默认颜色,没有动态网格没有触及过:它是所有的“静态网格区域”
为什么DOOM跳过静态网格的速度计算?因为静态像素速度可以从其深度简单地推断出来,而播放器的相机自上一帧起就是新的状态,所以不需要在每个网格的基础上进行计算。
速度图在稍后应用一些运动模糊将是有用的。

闭塞查询

我们想发送尽可能少的几何渲染到GPU,所以实现这一点的最好方法是剔除播放器不能直接看到的所有网格。DOOM中的大部分闭塞剔除都是通过Umbra中间件完成的, 但引擎仍然会执行一些GPU遮挡查询来进一步削减可见性集。

那么GPU遮挡查询背后的主意是什么?
第一步是将世界的几个网格组合成一个包含它们的虚拟框,然后要求GPU根据当前的深度缓冲区渲染此框。如果没有一个光栅化像素通过深度测试,这意味着盒子被完全遮挡,并且渲染时可以安全地省略该盒子中的所有世界对象。
那么事情是这些遮挡查询结果是不可用的,你不想阻止GPU管道阻塞一个查询。通常,读取结果被推迟到以下帧,因此有必要使算法有点保守,以避免对象弹出。

不透明对象的聚类前向渲染

现在渲染所有不透明几何和贴花。照明信息存储在浮动HDR缓冲区中:

照明25%
照明50%
照明75%
照明100%

深度测试功能被设置为EQUAL避免任何无用的overdraw计算,由于先前的深度预测,我们知道每个像素应具有哪个深度值。
当渲染网格时,贴图也直接应用,它们存储在纹理图集中。

它已经看起来很好,但我们仍然缺少一些透明的材料,如玻璃,或颗粒,还没有环境反射。

关于这个通行证的几个字:它使用一个聚集的前向渲染器,它受到Emil PersonOla Olsson的作品的启发。
历史上,前向渲染的一个缺点是它无法处理大量的灯光,在延迟中更容易处理。
那么集群渲染器怎么工作?
首先,您将您的视口划分成瓦片:DOOM创建一个16 x 8细分。一些渲染器将停在这里,并计算每个图块的灯光列表,这有助于减少照明计算量,但仍然受到一些边缘情况的影响。

集群相机结壳

群集渲染将从2D到3D进一步提升概念,而不是停留在2D视口细分上,它实际上通过沿Z轴创建切片来执行整个相机平截头体的3D细分。

每个“块”被称为“集群”,你也可以称之为“平截头体” 体素或“froxels”。
右侧是一个简单的4 x 2视口细分的可视化,5个深度切片将平截头体分为40个群集。

在DOOM中,相机平截头体被分为3072个群集(16×8×24个细分),深度片沿Z轴以对数方式定位。

使用集群渲染器,典型的流程是:

  • 首先,CPU计算影响每个集群内的照明的项目列表:灯,贴花和立方体…
    为此,所有这些项目都“体素化”,因此可以测试其影响区域与集群的交集。数据作为索引列表存储在GPU缓冲区中,以便着色器可以访问它。每个群集最多可以容纳256个灯,256个贴花和256个立方米。
  • 然后当GPU呈现像素时:
    • 从像素坐标和深度,确定其属于的集群
    • 检索该特定集群的贴花/灯的列表。它涉及偏移间接和索引计算,如下所示。
    • 代码循环遍历集群的所有贴花/灯光,计算并添加其贡献。

实际上,像素着色器可以在此通过期间检索灯光和贴花的列表:

应用灯光和贴花 – #1
应用灯光和贴花 – #2
应用灯光和贴花 – #3
应用灯光和贴花 – #4
应用灯光和贴花 – #5
应用灯光和贴花 – #6

还有探针列表(上图中未显示),可以以完全相同的方式访问,但在此通行证中未使用,所以我们稍后再回来。
在CPU上预先生成每个簇的项目列表的开销是非常值得考虑的,它可以显着降低GPU上的渲染计算复杂度。
集群向前渲染最近越来越受到关注:它具有处理比基本转发更多的亮度的好处,而比延迟更快,必须从多个G-Buffers写入/读取。

但有一些我还没有提到的:我们刚刚检查的这个通行证不仅仅是一个写给照明缓冲区的前进的一个; 而执行2次也使用MRT产生了2个G-Buffers :

法线贴图以R16G16浮点格式存储。镜面图在R8G8B8A8中,Alpha通道包含平滑因子。
所以DOOM实际上巧妙地混合了前进和推迟与混合方法。当执行附加效果(如反射)时,这些额外的G-Buffers将派上用场。

最后一件事我省略了:同时也产生了一个160 x 120的反馈缓冲区,用于大型纹理系统。它包含告诉流式传输系统哪些纹理应该流式传输mipmap级别的信息。
大型纹理引擎以反应的方式工作:在渲染传递报告后,某些纹理缺少引擎加载它们。

GPU粒子

计算着色器被分配到更新粒子仿真:位置,速度和寿命。
它读取粒子当前状态以及正常和深度缓冲区(用于碰撞检测),起到模拟步骤的作用,并将新状态存储到缓冲区中。

屏幕空间环境遮挡

在此步骤中,现在生成SSAO地图。

其目的是使狭窄的接缝,褶皱变暗。
它也用于应用镜面遮挡 以避免出现在被遮挡的网格上的明亮的照明伪影。

在从深度缓冲区,正常和镜面地图读取的像素着色器中,以原始分辨率的一半计算。
获得的第一个结果是嘈杂的。

屏幕空间反射

像素着色器现在生成SSR映射。它仅使用存在于屏幕上的信息的射线追踪反射,使得光线在视口的每个像素上反弹,读取由它们击中的像素的颜色。

SSR地图

着色器的输入是深度图(用于计算像素世界空间位置),法线贴图(知道如何使光线反弹),镜面图(知道反射量)和前一帧渲染(在pre-tonemapping阶段,但后透明度,有一些颜色信息)。先前的帧摄像机配置也提供给像素着色器,因此可以跟踪片段位置的变化。

SSR是一个不错的,不太贵的技术,可以在现场进行实时动态反射,以保持不变的成本,真正有助于沉浸和现实主义的感觉。
但由于它纯粹在屏幕空间中工作,缺少“全局”信息,因此它自带的工件。所以你可能会在一个场景中看到很好的反射,但是当你开始向下看时,反射的程度会减少,直到看到你的脚就完全没有反思。我发现DOOM中的SSR很好地整合在一起,它们提高了视觉质量,但是除非你真正关注他们,否则你不会注意到它们消失。

静态立体图反射

之前所有的动态反思(及其限制)现在都来自使用IBL的静态反射。
该技术基于预先生成的128 x 128立方米表示地图不同位置处的环境照明信息,它们也称为“环境探测”。正如我们以前在截锥体聚类中所看到的光和贴花一样,探针也按照与每个聚类相同的方式进行索引。
级别的所有立方体都存储在一个数组中,其中有几十个,但这里是这个场景的5个主要贡献者(这个房间内的立方体):

像素着色器从深度,正常的镜面缓冲区中读取立方体影响像素的群集结构(立方图越靠近其影响越强)并生成静态反射图:

静态反射图

一起混合地图

在此步骤中,计算着色器组合了之前生成的所有映射。
它读取深度和镜面地图,并将前进的照明与:

  • SSAO信息
  • 当SSR可用于有问题的像素时
  • 当SSR信息丢失时,静态反射映射数据被用作回退
  • 还计算了一些雾效应
混合+雾:以前
混合+雾:之后
混合+雾:之后

粒子照明

我们在这个场景中有一些烟雾粒子,照明实际上是根据精灵计算的。
每个精灵的渲染就像是在世界空间中:从它的位置,一些光列表和它们各自的阴影贴图被检索,并且四边形上的照明被计算出来。然后将结果存储到4k地图集的瓦片中,基于距离相机的粒子距离,瓦片可以具有不同的分辨率,质量设置…图集具有相同分辨率的精灵的专用区域,这里是64 x的概述64个精灵:

粒子照明图集

而这只是以这种低分辨率存储的照明信息。之后,当实际绘制一个粒子时,使用全分辨率纹理,并将照明四面体放大并与之混合。
这是DOOM将粒子照明计算与游戏的实际主渲染分离的位置:无论您在播放的分辨率(720p,1080p,4k …),粒子照明总是计算并存储在这些微小的固定大小的图块中。

低档和模糊

场景被缩放了几次,下降到40像素。使用分离的垂直和水平通过,最小的缩小水平模糊。

为什么这个模糊如此早?这样的过程通常在后期处理过程中完成,以从明亮的地方产生绽放效应。
但是,在渲染玻璃折射时,所有这些不同的模糊级别将在下一次通过中派上用场。

透明对象

所有透明物体(眼镜,颗粒)都呈现在场景之上:

所有透明物体(眼镜,颗粒)都呈现在场景之上:

透明对象:之前
透明对象:之后

眼镜在DOOM中呈现非常好的特别是磨砂或肮脏的眼镜:贴花仅用于影响玻璃的某些部分,使其折射或多或少模糊。
像素着色器计算折射“模糊度”因子,并从模糊链中选择最接近该模糊因子的2个图。它从这两个地图中读取,然后在2个值之间线性插值,以逼近折射应具有的最终模糊颜色。这是由于这个过程,眼镜可以在基于像素的基础上以不同的模糊水平产生良好的折射。

失真地图

非常热的区域可以在图像中产生热变形。在这里,戈尔巢稍微扭曲了形象。

对深度缓冲区进行失真,以创建低分辨率的失真图。
红色和绿色通道表示水平和垂直轴的失真量。蓝色通道包含要应用的模糊量。

实际效果稍后应用于使用失真图的后期处理来知道应该移动哪些像素。
虽然在这个场景中,特别是只有一个微妙的失真并不明显。

用户界面

UI被渲染到以LDR格式存储的预乘法alpha模式中的不同渲染目标。

将所有UI都放入单独的缓冲区,而不是直接绘制在最终的框架之上,这样的优点是游戏可以一次性在所有UI小部件上应用一些过滤器/后处理,如色差或视觉失真在单程中。

渲染不会特别使用任何批处理技术,它会逐个绘制UI项目,大约有120个绘图调用。
在后面的过程中,UI缓冲区被混合在游戏图像之上,以产生最终结果。

时间抗锯齿和运动模糊

使用速度图和先前帧的渲染结果应用TAA运动模糊
片段可以被重新投影,因此像素着色器知道当前正在处理的像素位于前一帧中。渲染实际上每隔一帧将网格投影稍微移动一半像素:这有助于删除子像素的别名伪像。

TAA和运动模糊:之前
TAA和运动模糊:之后

结果是非常好的:不仅网格边缘变得光滑,而且镜像混叠(其中一个亮像素将单独弹出一帧)也被照顾。通过像FXAA这样的后期处理方法,质量远远好于可以实现的质量。

场景亮度

该步骤计算场景的平均亮度,这是晚些时候提供给tonemapper的参数之一。

HDR照明缓冲区在一个循环中被缩减到其分辨率的一半,直到它变为2×2纹理,每次迭代计算像素颜色值作为其较高分辨率图中其4个父像素的亮度的平均值。

盛开

应用亮通滤镜来调暗场景最黑暗的区域。

然后,亮通滤波器的结果在一个循环中被缩小,并且在我们之前看到的类似过程中模糊。

层被模糊,高斯模糊分为垂直和水平遍,其中像素着色器计算沿着一个方向的加权平均值。

然后将模糊的层组合起来,以创建在原始分辨率的1/4的HDR纹理的绽放。

最终后期处理

所有这一步都在一个像素着色器中执行:

  • 读取失真图数据应用热失真
  • 在HDR照明缓冲区的顶部添加了绽放纹理
  • 执行如渐晕,污垢/镜片闪光的效果
  • 通过对2×2亮度图的中心进行采样并使用附加的曝光参数来获取平均亮度,应用调色板和颜色分级。
Tonemapping:之前
之后

tonemapping采用HDR照明缓冲器,其颜色在广泛的亮度范围内变化,并将其转换为每个组件(LDR)的8位,因此可以将框架显示在显示器上。
使用基于方程式的电影节拍操作员(x(Ax+BC)+DE) / (x(Ax+B)+DF) - (E/F),它是“ 神秘海域2”色谱图,也出现在GTA V中

请注意,场景的所有普通红色都来自于颜色校正。

UI和电影谷物

最后,UI被混合在游戏框架的顶部,同时应用微妙的胶片

UI – 电影谷:以前
UI – 电影谷歌:之后

唷!我们完成了框架,现在可以将其发送到监视器进行显示,这是相当多的计算,但所有这些都发生在不到16ms。
DOOM通过巧妙地重新使用以前帧中计算出的旧数据,从而高性能地产生高质量的视觉效果。总共有1331个绘制电话,132个纹理和50个渲染目标。

奖金备注

玻璃上的特写镜头

玻璃渲染是非常好的,它是通过我们以前看到的相对简单的步骤实现的:

  • 准备几层不透明网格渲染的模糊
  • 在正向模式下使用贴花/照明/探针反射在前方绘制半透明物品,使用先前的链条进行不同的玻璃折射模糊值,因此每个像素可以具有自身的折射值。
玻璃:以前
玻璃:之后

景深

在分析中研究的框架没有真正显示任何景深,所以让我们考虑在应用DoF之前和之后的以下场景:

DoF:之前
之后

并不是所有的游戏都能正确执行DoF:天真的方法通常是使用高斯模糊,并根据像素的深度在一次通过中进行所有的模糊。这种方法简单而便宜,但有几个问题:

  • 而高斯模糊对于绽放来说是不错的,创造散景是不正确的:你真的需要一个平坦的内核,使一个明亮的像素的光散开在一个圆盘或六边形的形状…高斯不能创造漂亮的散景形状。
  • 在单次像素着色器中执行DoF可能会导致出血伪像。

DOOM正确地执行DoF,选择的方法是在我的经验之中给出最好的结果:

创建远场和近场图像:根据其深度和DoF参数完成像素选择。

  • 近场可以强烈模糊,它会越多流入像素后面越好。
  • 远场也模糊,但没有从对焦/近场区域读取任何像素,因此避免了前景对象错误地渗透到背景中的任何问题。
远场
远场 – 模糊#1
远场 – 模糊#1&#2

为了创建散景模糊,DOOM以半分辨率工作,并使用64个纹理抽头执行磁盘模糊,每个样本具有相同的权重,因此亮度实际上不像高斯模糊那样蔓延。
磁盘直径可以根据像素的CoC值在每像素的基础上变化。

然后,它进一步扩展了16分针模糊的模糊,但这次它不计算加权平均值,它只是累积采样值并保持邻居水龙头的最高值,因此不仅会扩大第一个模糊它也修复了第一次通过的小物体(采样间隙)。最后一部分是McIntosh的工作灵感。
考虑到获得的最终磁盘模糊的宽半径,这种在多次通过中的迭代技术可以产生非常好的大模糊,同时仍然保持有效的性能,每像素执行的实际纹理抽头的数量仍然相当低。

远场和近场图像最终通过Alpha混合在原始场景的顶部合成,以创建最终的景深效果。这个通过在应用运动模糊之前执行。

更多阅读

如果您想更深入地了解idTech 6技术,幸运的是有很多演讲和公共材料可供选择:

关于这个话题的进一步讨论: SlashdotHacker NewsRedditKotaku

加入讨论

电子邮件地址不会被公开。 必填项已用*标注

www.000webhost.com