《荒野大镖客2》是一款备受瞩目的开放世界动作冒险游戏,由Rockstar Games制作并发行。作为该公司最新力作,其画面效果高度还原了美国西部的风光和人文风情,使得玩家仿佛置身于广袤的大草原和险峻的山峰之中。作为现代游戏业中图形学技术的杰出代表之一,本文将围绕《荒野大镖客2》的多边形表现和图形渲染技术进行探究,并阐述其在游戏设计方面的创新之处。
荒野大镖客2图形学研究报告
《荒野大镖客》一直都是我最喜欢的游戏之一,在2018年的时候,它在主机上回归,发布了一作前传。后来在2019年登陆了电脑端。我终于能玩上这个游戏,并且一打开游戏,就对他的图形效果感到震惊。但是因为我基本上没办法在1050Ti GPU的笔记本电脑上玩中等画质跑到25FPS,所以我很失望。我知道是我的装备不够好,但是也不至于在中画质跑不了25帧吧?
今天,我们就看一下游戏里的一些抓帧结果,然后来试着分析一下游戏里使用的图形技术。
前言这不是官方对游戏的拆解。这只是我自己用RenderDoc抓帧分析。如果你想从他们的开发人员那学习,可以看Fabian Bauer在SIGGRAPH的演讲ppt。幻灯片(在页面的底部),视频(从1:58:00开始)。
也可以看一下Adrian Courrèges对GTA 5的图形学分析。因为RDR 2和GTA 5是一家公司开发的,用的都是同样的引擎,所以一些技术也可以在GTA 5的文章里找到。
另外一个重要的事情是,我不是一个高级图形程序员或者之类的职位,我还是一个图形学领域的大三学生。所以很多东西我不懂。如果你发现任何错误和可以改进的地方,请联系我。那么开始吧!
逐帧解析这是要解析的主要的一帧:
抓取于电脑,中等画质RDR 2是一款开放大世界的游戏,数据的加载都是不间断的。因此,这一帧是从一系列的任务开始的,比如创建和删除一些贴图,一些Shader资源(注:常量Buffer,贴图 Buffer,贴图或者采样器),用shader访问的一些无序资源,更新描述符,缓冲区等等。
泥浆图(Mud map)泥地在游戏里非常重要。除了作为一种游戏机制,他还让环境变得更加逼真。这个游戏把人和马的脚印贴图,连同马车车轮的轨迹纹理渲染到一张置换贴图(Displacement map)上。这个累加的贴图,会在渲染地表时用来做视差遮挡映射(Parallax Occlusion Mapping)。
Mud map: 2048x2048 R16_UNORM 天空和云泥地渲染的pass之后,游戏在GPU运算上做了很多的工作。大部分的工作都跟天和云有关。云,雾,和体积光是RDR2 的一些突出效果。你可以在 Fabian's的幻灯片里找到更多关于这个阶段的信息。他比我解释了更多的细节。
环境贴图环境贴图是RDR 2和GTA 5里面做反射的主要方式。像GTA 5一样,RDR 2是从相机的位置,生成一张环境的Cubemap。它生成一个比较小的G-Buffer来存储环境贴图,类似于《孤岛惊魂4》。
Environment Cubemap Faces (Albedo): RGBA8_SRGBEnvironment Cubemap Faces (Normal): RGBA8_UNORM Environment Cubemap Faces (Depth): D32S8每一帧都生成环境Cubemap的话,任务太重。RDR 2做了一些优化来降低这种消耗。例如,游戏只绘制静态和不透明物体,在渲染每一个面之前,先用视锥体去剔除,并且绘制的是低LOD的模型。即使如此,我发现环境贴图在地形上的多边形数量还是非常的高。
在G-Buffer的pass之后,生成了一张用天空剖面贴图(注:感觉应该是Dual-Paraboloid Map)和跟云有关的贴图结合来的天空环境CubeMap。下一步是卷积。RDR 2对给予图像的光照用的是分割和求近似的算法(注:一个Epic Game提出的解决方案,来实施的对高光进行预卷积)。这个方法用了一个预过滤的环境CubeMap和环境BEDF LUT(双向反射分布函数)(注:Lookup Table)。过滤的过程是先对环境的CubeMap进行卷积,然后将结果分别存到CubMap的一系列Mipmap里。
在为环境Cubmap执行光照pass之前,RDR 2把一张烘焙好的大尺寸环境遮蔽光渲染到另外一张Cubmap的贴图上。游戏用的是屏幕空间的环境遮蔽光,因为屏幕空间遮蔽光(Screen Space Ambient Occlusion)可以只占用很小尺寸的的贴图。烘焙环境遮蔽光有助于在一些比较大片区域的地方让环境变暗,比如让天井和室内变暗。
Environment Cubemap Faces (Baked AO): R8_UNORM游戏使用的是基于格子的延迟渲染(TBDR)路径来计算环境的光照映射。每一个环境映射的面的光的剔除和光照在一个pass里被一起计算。(感谢@benoitvimont 指出这一点)游戏也用了“自上而下的世界光照映射”技术来烘焙光照,这一点与《刺客信条 3》类似。
对于CubeMap的每一个面,RDR 2把最终结果的颜色渲染到天空环境纹理的最上一层。然后对环境CubMap进行和天空环境CubeMap一样的滤除。
Environment Cubemap Faces (Final): R11G11B10_FLOAT当玩家靠近建筑物时,建筑物内部的环境贴图才会被加载。从硬盘上加载CubeMap G-Buffer的时候也是这样。
Baked Environment Cubemap Faces (Albedo): BC3_SRGB (Baked AO stored in alpha channel) Baked Environment Cubemap Faces (Normal): BC3_UNORM Baked Environment Cubemap Faces (Depth): R16_UNORM对这些光照贴图的计算和过滤跟前面说的一样。游戏同时只会计算一张烘焙好的环境贴图并且只会在一天中时间发生变化的时候重新计算他们。所有的环境贴图都存在一个CubeMap贴图数组里。这里没有CubMap用了双抛物面映射转换(dual-paraboloid map)。
G-Buffer Pass这一阶段从地形的深度预处理pass开始,然后讲一下把场景渲染到G-Buffer中。
GBuffer 0 RGB GBuffer 0 A RGB A8_SRGB :这个buffer包含了albedo(基础色)在RGB通道里。我不确定透明通道里的数据是做什么的,但是他是用在抗锯齿的阶段了。GBuffer 1 RGB GBuffer 1 A RGBA8_UNORM: RGB通道里是法线信息,透明通道里的信息跟布料和头发有关。GBuffer 2 RGB GBuffer 2 ARGBA8_UNORM: 对象是材质属性。R:反射率(f0)G:光滑度B:金属度A:包含一些阴影(这个通道会在后面的阶段作为Shadow Mask 使用)GBuffer 3 R GBuffer 3 B RGBA8_UNORM: 红色通道包含了坑,蓝色通道的内容也是个迷。透明通道存的信息跟头发的渲染有关系。绿色通道什么都找不到。GBuffer 4 RG RG16_FLOAT: 这个Buffer里存的是运动模糊的屏幕空间速度。GBuffer 5 Depth GBuffer 5 StencilD32S8: 像GTA 5一样,RDR 2也使用反转Z来记录深度,并且用Stencil Buffer来给特定组的mesh分配特定的值。
还有另外一个对象是从烘焙信息里生成的:
GBuffer 6 R GBuffer 6 G这个Buffer在红通道包含了烘焙的环境遮蔽光,就跟环境映射阶段的一样。但这个贴图里还有别的通道。绿通道里的数据看着跟GBuffer 3里的蓝通道很像。这个我也不知道是做什么的。我在我的截图里也找不到蓝通道和透明通道里有任何数据。我之后会进一步研究这个。
阴影映射贴图的生成在G-Buffer的阶段之后,游戏开始渲染阴影贴图。用了一组2D贴图来渲染点光源阴影贴图和一组cube贴图来渲染点光源阴影贴图。
有些游戏会用很大的阴影图集来存储阴影映射(比如:《毁灭战士4》)。这种方法的优点之一是可以依赖距离来调整阴影映射贴图的尺寸。当时用贴图组的时候,就没有这种灵活的使用策略了,因为每一张贴图的尺寸都必须一致。RDR 2 有三种不同的贴图组来对应不同的画面质量。例如,锥光有:
512 x 768 D16用于远处的灯光1024 x 1536 D16 适用于中等距离(以及中等画质时的更近距离)的灯光2048 x 3072 D16 用于近光灯(高/超高画质)点光会在所有方向上投影。为了解决这个问题,游戏里使用了一种叫做全向阴影映射的技术(Omnidirectional Shadow Mapping)(注: 一般平行光是生成Shadow Map,点光源也可以用,不过要更加复杂一些,称为 omnidirectional shadow maps,生成的是 cubemap),这种方式会把场景从相机位置渲染到一个深度的CubeMap上。篝火和亚瑟灯笼的油灯生成的阴影就是用这种技术渲染的。点光的阴影有三组,跟锥光一样在不同的画质分别使用。
游戏里的大部分静态点光源都烘焙了阴影的CubeMap。所以游戏尽可能的使用烘焙好的阴影贴图,只有当玩家靠近光源的时候才会生成阴影贴图。不过这让情况变得更有意思了。
墙上的大多数光源都是锥光,游戏不会生成全向阴影贴图,并且把阴影贴图复制到点光源的阴影CubeMap内存里。
左边的图是1024*1536的锥光阴影贴图,右边的图是相同的图像数据512*512的Cube形式的贴图。这解释了为什么他们不用正方形的阴影贴图来存锥光的光照信息。锥光阴影的像素数量和点光的CubeMap贴图必须是一样的。我确信你也发现了右边这张图是一条一条的。这是因为锥光和点光的阴影贴图宽度不同导致的。
还有一点,这个贴图并没有360度全覆盖。但幸运的是,建筑物上的光照通常背面有一面墙,烘焙的阴影贴图可以覆盖到它。
另外还有一件有意思的事情是这个过程反之亦然。例如,在圣丹尼斯——游戏里最大的城市之一——游戏生成了锥光的全向阴影贴图然后把这些数据拷贝到锥光阴影贴图组里。我不知道为什么RDR 2要这样做阴影映射。我没办法在网上找到任何相似的技术。
RDR 2里的平行光的阴影映射和GTA 5里面的几乎一模一样。逐级阴影映射( Cascaded Shadow Mapping)有四个级联。每一个级联有1024*1024图素组成的一张1024*4096(中等画质)的图集。
Directional Light Shadow Atlas: R16_UNORM 灯光阶段最后要把所有这些环境贴图,G-Buffer,阴影贴图,AO Buffer们结合到一起。
这个阶段包含两个Pass:第一个是全局光照(太阳/月亮),第二个Pass是局部光照。
全局光照Pass对于月光,游戏渲染了一全屏的Quad来计算平行光光照。有一部分烘焙的光照是来自上文提到的“自上而下的世界光贴图”。
局部光照Pass在这个Pass里,游戏渲染了一个低面数的球体来计算点光源的光照量和一个类似八面体形状的物体来计算锥光光源的光照量。灯光被从后向前渲染,并叠加混合。
为了避免调用一些不必要的Shader,游戏用了深度范围检测(DBT),这是一个OpgenGL/D3D11的扩展功能,不过在Vulkan/D3D12上已经自带了。还用了模板检测来剔除一些像素来减轻透明物体,比如窗户玻璃这样的消耗。这些物体会在Foward Pass被渲染。
水的渲染和反射像前面说的,环境贴图是反射的主要来源。一般的反射,像窗户的反射,都会用到这个。另一方面,镜子是用平面反射渲染的,你会从反射的方向再一次渲染场景。这个过程也是延迟渲染处理的。水的反射是屏幕空间反射结合了这一帧最开始生成的环境贴图。前向着色阶段延迟渲染管线的缺点之一是你没办法把半透明材质用G-buffer正确渲染。为了解决这个问题,本作会像大多数使用延迟渲染的游戏一样,用前向着色来从后向前的渲染半透明材质。
但是这种前向Pass会很耗,因为:
它用的前向着色Shader比延迟渲染的更费。Shader中使用的寄存器数量和平行运行时的Shader实例数呈负相关。因为前向着色结合了材质和光照(和阴影)的计算,寄存器的数量在前向Shader里可能会很高。它把每一个有厚度的半透明物体画了两次。为了实现正确的混合,你必须先渲染物体的背面,然后再渲染他的正面。因此,大多数的半透明物体在这个阶段都会被画两次。像2D-Quad这样的物体(比如窗户)会被渲染一次。并且每一次绘制之间都要进行一个渲染管线的切换。为了能够在正面和背面剔除之间切换,你就必须改变渲染管线的状态。而且这些改变很费。这个阶段还生成了另外一个渲染目标来实现Bloom的效果。这个目标存了Bloom的强度。正如你在图片中看到的,版透明物体的Glow更强。
Bloom intensity target: R8_UNORM强调一点,Bloom的强度会随着距离的增加变强,以在有雾的区域获得更多的辉光效果。
后处理这一阶段,是处理抗锯齿,Bloom,动态模糊,DOF和其他特效的时候。我计划再写一篇博客来专门写一下后处理、所以我这里不会说太多,但是我想说一些关于Bloom的部分。因为体积光的原因,主渲染对象已经有一些灯光的辉光效果了。
RDR 2的Bloom实现方式跟《使命召唤:高级战争》里的次时代后处理效果描述的很像。
输入目标没有阈值R11G11B10_FLOAT 7-mip的渲染目标13-双线性长度的滤波器来处理降采样,3*3tent的滤波器来处理放大尺寸。游戏之后将这个经过过滤后的Bloom对象和主要的渲染对象以及Bloom强度对象结合在一起了。
结论第一件让我注意的事情是这个游戏有许多计算和图形之间(注:感觉说的是CPU运算和图形学处理)的切换,反之亦然。如果启用,会执行异步计算。(你不能在游戏内开启异步计算的设置。你必须在游戏的配置文件里开启。)例如,仅针对Bloom特效;游戏切换去计算(注:应该说的是CPU的计算)并做了一些工作,然后切换回图形(注:应该说的是GPU的处理)并做降采样,然后再切换回计算做一些别的工作,然后又一次切换回图形做升采样。这些“切换”在GPU上不是很费吗?从计算中得到的收益是否可以证明来回切换的成本是合理的?另一件事是RDR 2清理了大部分的贴图。这一点很奇怪,因为游戏通常会避免一些不必要的贴图清理(e.g. 清理G-Buffer)。这些“纹理清除”对性能有什么真正的影响嘛?他们真的有必要清除这些贴图吗?还有一个奇怪的就是,在一帧里,有三个(可能更多)一样的深度降采样Pass。一个是做SSAO,一个是SSR,另外一个是体积雾和容积光的生成阶段。为什么游戏不在另外两个阶段使用SSAO的降采样深度对象?不要误会,我不是在这里指责什么(好吧……我对于帧率这么低还是有点失望的),我正在试图理解为什么要做这些决定。毕竟,很多天才员工都在这个游戏里努力工作。可能他们只是没有足够的时间更进一步优化游戏的性能。
最后好的,就这样!RDR 2是一个外表非常华丽的游戏。不仅仅是图形学技术,艺术方面,灯光,每一样东西都看起来很棒。我已经爱上了这个游戏的色调。尤其是晚上的时候,他让我想起了《神枪手之死》、《老无所依》和其他那些35mm胶片的西部电影。
荒野大镖客2以其惊人的多边形技术和细致入微的细节设计成为了一款备受称赞的游戏。这为游戏界的未来开创了新的发展方向和众多的可能性,也为无数玩家带来了不一样的游戏体验和感受。我们期待着更多优秀游戏的诞生,让玩家们能够享受更加逼真、美妙的游戏世界。