/【Siggraph编译】如何创造《荒野大镖客2》的大气世界

【Siggraph编译】如何创造《荒野大镖客2》的大气世界


Siggraph 2019(计算机图形学界的重大会议)于2019年7月28日-8月1日在洛杉矶召开。会议上RockStar做了这篇报告,它介绍 Rockstar 的《荒野大镖客2》游戏中的天空渲染技术,包括云/雾渲染、体积效果以及生成的环境光照模型。这套技术共同代表了团队如何在《荒野大镖客2》的开放世界中营造一种自然氛围。这些技术的构思和设计既符合光传输的物理特性和定律,更重要的是,还提供了强大的手段对于可指导性,允许艺术家精心打造我们的自然环境,以创造一种强烈的氛围感,始终支持和增强游戏的故事、任务和情绪。

《荒野大镖客2》是一款开放世界西部游戏,具有动态时间、天气周期和气候区域,而这些需求所包含的效果实现中有很多地方需要使用volumetric-based的方法,《荒野大镖客2》在本文中给出的实现方式可以在主机上保持30fps的帧率。

先来看下这些体积效果背后的一些物理基础知识:在所有进入到人眼或者说相机的光线中,这里只考虑单次散射的部分。光线(比如太阳光)在大气中传播,如图中的明黄色箭头所示,以大气介质中的一个粒子为例,假如这个粒子正好处于光线传播的路径之上,那么这个粒子对于光线的散射作用就会影响到光照进入到人眼后的效果。打到粒子上的光线,首先会被吸收一部分(这一部分会转化成其他能量,比如热能),用红色小圆圈表示,剩余的部分则会被散射开来。如果散射光线中存在着朝向相机的部分的话,那么这部分就会被计入到光线的in-scattering中,对应于上图中水平的橙色箭头,剩下的部分则会继续传播并与其他粒子或者表面再次产生散射交互,这种发生多次碰撞散射的特性被称之为multi-scattering,multi-scattering比较复杂,想要解析出这部分数据会比较困难,因此《荒野大镖客2》只考虑单次散射的作用。
《荒野大镖客2》认为在单个系统中模拟尽可能多的体积效果非常重要,希望通过一套统一的方案来实现对尽可能多种类的体积效果的模拟。这套方案应该要能够实现不同尺寸的体积效果,且能够实现interior效果跟exterior效果的无缝过渡。它应该尽可能地遵循物理模型,并且可针对不同平台进行扩展。
《荒野大镖客2》此次主要从以下几个方面展开:
1
 数据模型  
这一部分是体积效果对应的数据模型,主要讲述了体积效果计算需要哪些数据,这些数据是如何表示的。我们将了解如何处理云和雾的放置,以及天空散射和透射率。

云层的渲染位置是通过云层贴图(Cloud Map)来确定,云层贴图是一张覆盖全地图俯视角贴图,包含两个通道,每个通道对应于某个高度的云层的密度,云层贴图的生成会考虑天气跟当前时辰的影响。知道在 2D 中放置云的位置,仍然需要为它们提供基于高度的形状来模拟多种云类型。高度到云层形状的映射是通过一个一维的LUT实现的,LUT中包含了高度的数个梯度数据,而梯度数据可以映射到cumulus云层形状以及stratus云层形状。

云层的高频细节数据则是根据前面得到的云层贴图&LUT等低频数据按照一定算法雕刻得到。在样本的xy和xz位置对2D位移矢量图进行两次采样,并生成准3D矢量,之后会用这个向量以及风力向量来对3D的噪声volume贴图进行偏移计算出对应点的噪声数值,并据此计算云层的外部细节。

只包含云层基础形状的效果图

云层细节的实现

使用displacement vector对噪声进行干扰之后的输出效果

局部雾效可以通过查表实现,类似此前cloud map的处理,且只覆盖当前场景中玩家可能会涉及到的一些区域。Fog map的生成则是噪声贴图在天气以及TOD参数作用下的结果,因此可以实现早晨水面上的雾气随着时间逐渐消散的效果。fogmap是会随着游戏的需要,在参数变化的时候进行数据更新的。

另外一种实现局部雾的方式是fog volume——体积雾。体积雾的生成位置可以通过美术同学手动放置的方式来指定,也可以通过绑定到对应的粒子或者物件上的方式来指定。体积雾的体积形状通常为球形或者方形,混合方式通常为Additive、Alpha blending。
假设当前fragment shader计算得到的颜色值为(comp_color,comp_alpha),而此前像素的输出颜色值为(frag_color, frga_alpha),那么:
Alpha Blending: comp_color * comp_alpha + frag_color * 1- comp_alpha)
Additive Blending: comp_color * comp_alpha + frag_color
二者的区别在于,是否需要对原有的像素颜色进行alpha衰减,从这个角度来看,alpha blending中的当前计算输出结果占比要高于additiveblending,即Additive Blending作用下,背景会更通透些。

局部体积雾在室内观察的实现效果,此时的雾效采用的是alpha blending的方式实现

雾效在户外的效果展示,此时的雾效通过Additive的方式渲染
2
  渲  染  
这一部分是数据模型在渲染中的使用,主要讲述了散射光照的计算实现,以及使用frustum跟raymarching方式实现散射渲染时,《荒野大镖客2》采用的一些实施策略,并在后面附上相应的性能表现数据。
散射光


就散射光,对于近景而言,《荒野大镖客2》使用了一个跟frustum平齐的volume实现数据的采样;而对于远景,使用的则是raymarching系统。这样做的目的是兼顾表现跟效率,而不论是近景还是远景,这两套方案对应的光照以及数据模型都是相同的。

《荒野大镖客2》在每个样本位置应用的着色模型侧重于单散射行为和与波长无关的透射率。多次散射效果则是使用两个直接光照octave来模拟。先来回顾一下散射光强计算公式:所有参与到散射输出的光源,在经过相函数P以及可见性函数V(散射采样点处是否对于光源而言是否可见)调制之后,求和输出。《荒野大镖客2》的可见性结果可以通过shadowmap获取,或者通过secondary ray T进行查询(指的是太阳光在第二层云层处投射的射线)
《荒野大镖客2》的相函数的选取兼顾了效果以及性能,力图采用低消耗的相函数实现对高消耗散射效果的逼近。

得到的相函数能够很好的给出前向散射的表现,不过对于后向散射还需要想其他办法。《荒野大镖客2》给出的是一个并不物理的解决方案:基于Lambertian BRDF计算公式。
平截锥体体积
前面介绍的是在某个采样点上进行散射光强累加计算的算法,下面看下所有采样点的组织方式。
对于近景区间,《荒野大镖客2》使用的是frustum volume方法,也就是所谓的froxels方法。frustum覆盖的深度区域是可以动态调节的,最高可以达到160m,不过在某些情况下,比如室内,美术同学也可以将之调的比较小,以降低采样率过低导致的问题。之后会通过三个阶段,每个阶段对应于一个volume来完成数据的整理与搜集:方向光阴影、材质以及散射光。
volume本身的分辨率较低,即使在TAA的作用下,想要得到高清细节也是不现实的。因此这类需要另外一个系统来接管volume系统在高清距离情景下的表现。Raymarching是一个内存消耗较低的方法,不过其迭代过程会需要较多的计算时间,因此这里可以选择舍弃一些散射效果,比如fog olume,irradiance probe以及局部光照来实现加速。
光线步进

Raymarching中每条射线的长度是有限制的,其最大长度取决于深度buffer以及与地表和云顶的理论上的交点。为了降低marching的消耗,需要进行修正:每次marching发生碰撞后,就会先回退至上一个采样点,并将步进长度减半进行再次marching。这个marching修正过程会有一个固定数值的迭代次数,因此最终追踪到的碰撞点距离理想点的位置不会差太远
这里的目标分辨率是屏幕分辨率的一半,虽然很低,但是对于这里所需要的情况而言是足够了,不过在这个分辨率下,如果对于每个像素都生成一条射线依然太过昂贵,因此,这里会尝试使用更少的射线,并在后面进行一个重建过程来优化。
对于这个模式而言,会在半分辨率贴图空间中,每2x2个像素中取一个像素出来进行射线计算,之后剩下的3个像素则取用前一帧的结果。为了避免结果相差过远,会在取用前一帧结果的时候对数据进行clamp处理,clamp的范围为相邻的3x3像素组成的颜色AABB。此外,还会通过放弃跟当前像素深度相差过大的像素的方式,来避免结果产生较大的深度断痕。为了使效果更显著、减少闪烁,《荒野大镖客2》进行了多种尝试。
表现

性能表现跟所使用的数据,场景的深度以及相机朝向都有很大关系,整个系统中消耗最高的就是raymarching,这个过程是通过shader loop完成的,因此消耗取决于射线长度以及迭代次数。
3
  场景整合  
这一部分是场景与数据模型之间的关联,主要讲述了数据的填充与更新是如何完成的,尤其是如何对于环境光方向光以及反射效果造成影响。

这里给出的天空散射方案是基于Bruneton07的预计算大气散射方案,不过《荒野大镖客2》认为地球阴影对于开放世界TOD的效果提升比较显著,因此还支持了地球阴影。LUT会根据时间片来进行更新。天空散射以及透光率数据会被缓存在另一个跟frustum一致的volume中,可以同时用于前向跟延迟渲染。

出于对性能的考虑,不会对每个采样点都进行天空散射的采样计算,作为近似值,改为在光线行进后在透射率加权得到的深度处对其进行一次采样。
此外,为了支持light shaft效果,《荒野大镖客2》还将可见性项Visibility从天空散射积分中分离出来。在Raymarching过程中,会对通过对阴影贴图进行查找而得到的方向光的light可见性进行累加,并根据当前采样点覆盖的面积以及当前射线对应的透光率对这个累加项进行加权。射线raymarching终止后,再根据射线的全长度对该值进行归一化处理,之后使用这个归一化后的数值来对天空散射数值进行调制。这种做法虽然不符合物理规律,却可以用较低的消耗给出比较令人满意的结果。

现在,我们拥有了为大气散射解决方案合成最终图像所需的一切。在所有不透明物体绘制完成之后,透明物体绘制之前,就需要将in-scattering、transmittance volume还有raymarching结果跟天空散射等数据应用到场景里。在高海拔区域,《荒野大镖客2》会添加一层卷云效果,这个效果会需要根据密度进行一次2D贴图采样,而光照模型则是跟之前描述的一样。

我们可以看到,云层跟雾效对于主场景的影响还是很显著的,而这两者对于全局光的依赖都比较高,因此有必要加入全局光照的考虑。为此,我们维护 32x32 probe,编码为3阶球面谐波。每个probe都放置在一个有序的网格中,其中高度和方向性都是从高度和弯曲的法线贴图中采样的。然后,每个probe将光线投射到世界中并累积散射和透射率。然后将结果应用于该方向的天穹的天空Rayleigh/Mie散射项。这种做法可以在洁净天气下实现蓝天的面光源效果,也可以实现阴天从云体中产生的间接光效果。

这组probe为更高分辨率的irradiance probe field提供了基础。为了加快速度,《荒野大镖客2》还单独设计了一个分类pass,在这个pass中会对主场景的数据可见性进行计算,可见的probe会分配更多的射线,不可见的probe也不能完全不管,因为反射计算可能会需要用到这些数据。射线的计算会通过多线程完成,每个线程负责一条射线,射线在半球上按照Hammersley排布,之后在共享内存中调用并行的reduction操作以生成SH系数。Raymarch 也被简化了,因此作实际上消耗不算很高,在PS4上大概是0.22ms左右。

无云层条件下的表现效果

添加了通过2D贴图采样得到的卷云表现效果

添加了Raymarching得到的大气散射效果后的表现

添加了近景Volume后的表现效果

添加了天空散射、衰减后的表现效果

对于反射实现而言,可能还会需要添加额外的射线raymarch操作。对于相机位置反射probe而言,只需要生成一个包含散射和透射率数据的低分辨率cubemap即可,因而可以跟天空的irradiance probe计算一样,直接使用简化版的Raymarch来降低消耗。
我们能够看到《荒野大镖客2》所实现的方法效果还是十分显著的,那么我们来对比一下几款游戏和《荒野大镖客2》在云雾渲染上的区别:

《荒野大镖客2》中的林间雾

《逆水寒》的林间雾

《永劫无间》的林间雾
我们能够对比看出来三款游戏在风格上是有很大差异的,这也跟游戏本身想要带给玩家的内容有很大关系,《荒野大镖客2》希望玩家能够体验体验在19世纪的最后岁月里横跨美国的亡命之旅;《逆水寒》是一款武侠风的游戏,在画风上会与之不同;而《永劫无间》则会侧重玩家与玩家间的碰撞,三者的差异还是比较明显。不过如果将更多风格的云雾、光照系统带入到类似《永劫无间》这样的多人竞技游戏会不会让表现更加多样化,对此我们都是十分期待的。
往期推荐


本文来自微信公众号“网易雷火UX用户体验中心”(ID:LeihuoUX)。大作社经授权转载,该文观点仅代表作者本人,大作社平台仅提供信息存储空间服务。