自然界中,云雾是一种千变万化的状态,要把这套系统在游戏里做到真实一直是多数3A大作不断研究的课题。
11月22日,由腾讯游戏学堂举办的 TGDC 2021 腾讯游戏开发者大会于线上举行。
在本次技术专场上,腾讯互娱北极光工作室群的高级工程师鲍鹏分享了《天涯明月刀》的云雾大气系统打造过程。从建模、渲染和性能等方面详细的介绍了如何打造动态化体积云、体积雾的技术系统。
结合演讲中的效果演示,我们可以更直观感受《天涯明月刀》如何在细节方面给玩家来带极致沉浸感。
以下为演讲实录整理,略有删减:
大家好,很荣幸参加本次TGDC分享,这次我给大家带来的是《天涯明月刀》端游自研引擎的体积云和体积雾的渲染系统开发和优化分享。
首先给大家介绍一下背景:天刀在业内一直以动态的云海,体积云著称,算是最早一批应用体渲染的大型3D网游,主要思想就是借鉴体绘制,利用体渲染进行光线步进,整体效果非常真实,并且性能也是十分高效。
近年来各类3A大作在这方面的改进层出不穷,提出了各种各样的新技术,自然界中的云和雾千变万化,令人心旷神怡赏心悦目,新技术以及美术效果的各种需求使我们有了更高的追求和目标。
系统概述
现在给大家介绍一下我们的整体系统。如图所示,我们需要渲染相机中所看到的各种参与性介质,比如雾、云、大气等,在距离相机0-200m的范围我们使用Froxel,就是图中黄色的体素网格进行渲染。超出200m的范围使用RayMarch进行渲染。
这段视频演示了系统的部分效果。我们支持全动态的时间天气变化,美术可以任意设置云雾的形态,玩家可以随意穿越进入体积云和体积雾。
接下来我会详细讲下我们是如何对这些体积介质建模的。
建模
首先是体积云,为了方便讲解,我们先从一个简单的例子入手,图示的一块云是从10000米的高度往下看,这块云体大概覆盖了5km*5km的范围,它是根据左上角的2D俯视角的Cloudmap生成的,它在高度上没有变化。
我们需要引入一张高度LUT贴图来控制他在高度上的变化,左上蓝红色的竖条既是当前的高度LUT,云层在高度上便有了变化。最后我们加入displacementMap以及PerlinWorley3D贴图来刻蚀云体的边缘以及细节。两张贴图如左下角所示,最终云体模型会变得更有层次,细节丰富。
这个视频演示了Cloudmap变化对云体形状的影响,cloudmap如红色箭头指示所示。
这段视频演示了高度LUT贴图对云体形态的影响,LUT贴图如红色箭头指示所示,实时游戏中LUT的变化会插值进行过渡,我们还可以使用各种程序化参数来改变云层的形态,如这个程序化的云体漩涡的生成与动态变化。
接着是体积雾的建模,FogMap跟CloudMap的作用一样。它是张2D贴图,覆盖可玩区域,R通道在Houdini中以地形高度图为基础微调,G通道在Houdini中根据噪声生成,对特定区域进行手工编辑,B通道是浓度,实时程序化生成,这段视频演示了FogMap3个通道变化对地图中体积雾的影响。R通道控制起雾的高度,G通道控制起雾的衰减,最后B通道控制起雾的浓度。
接下来简单说下天空大气的原理。我们在外太空会看到地球上有一层大气光环,在晴天我们看到的天空是蓝色的,在黄昏时看到的天空是橙红色的,这都是因为地球上的大气所造成的现象。类地行星上存在着稀薄的气体以及其他细小颗粒,它们构成了一种特殊介质,会与光线有交互,形成各种现象。
如果空气颗粒比较细小,短波长的蓝光会受它的影响进而散射,这就是Rayleigh散射,散射出去的蓝光使天空变蓝。
如果是比较大的颗粒,光线会向四面八方散射,而它在传播主方向上散射的光比较多,这就是Mie散射,我们平常所见的雾霾就是Mie散射的一个例子。
Rayleigh散射变强的对比,在傍晚太阳光线在空气中传播距离比较长,导致蓝光大部分散射开来,天空呈橘红色。
Mie散射变强的对比,空气中大颗粒粒子比较多,会造成雾霾现象。
接下来是体素介质的建模。我们有时需要一些特殊的参与性介质,它的精度要求比较高,可能需要一些特殊的光照。它的运动轨迹可以由美术来设置。
我们在Houdini中根据力场生成带扰动的风场,来驱动这些介质,用两套实时计算的偏移场来混合介质的位移,可以看出视频中经历了3个完整周期的偏移场混合也基本没有力场重置和叠加的瑕疵。
渲染
渲染的章节我们分成4个部分,首先是Froxel的渲染,Froxel是Frustum+voxel的缩写,从字面上就可以知道,它是将相机的视锥分割为一个个的3D体素。
我们之前说了,使用froxel渲染的是200m以内的视锥,视锥分割成了体素后。
第一步我们会对每个体素注入介质的属性,同时我们对每个体素生成shadow mask来保存它们的可见性信息。第二步计算每个体素的光照结果。第三步将每个体素的光照结果沿相机视线方向进行离散化积分,得到最终的光照结果。此处展示了近处200mFroxel的渲染结果,太阳光是从左上角打下来,场景中有多个局部动态光源,这里的体积雾的建模会根据风力随机扰动属性值。
接下来是RayMarch的介质渲染,如图所示,我们会在Froxel后发射光线来继续渲染之后的参与性介质。
首先,需要计算光线的起点和终点,然后扰动起点位置来防止一些ray march的pattern。红色箭头指向的小图中,每个红点代表raymarch的采样位置。可以看到由于起点扰动,每个采样点并不是规则的齐步步进。
一开始我们会使用比较长的步长来快速步进没有介质的区域,当我们采样到介质时,我们会多次试探来确定一个比较好的步长,在采样到介质之后,会每次减半步长来回退。如果回退还在介质中则继续减半步长回退,直至离开介质,然后再次步进介质中。如此循环直至回退数达到设置的最大值。
在介质中我们会逐渐加大步进步长,当离开介质后,我们会重新使用更大的步长来步进。
(ray march渲染的体积雾和体积云)
下面我们详细讨论下光照模型,我们计算相机通过介质看到的最终颜色。首先要知道背景颜色,就是图中xs这一点的光照颜色,考虑到空气中有参与性介质,背景颜色会根据穿过介质的透射率(图中Tr项)做融合,最终的颜色除了背景色还有介质本身受光的颜色,我们需要在视线方向上做积分来计算介质本身的颜色。
如图所示,我们有多个绿色的离散采样点,需要计算这些采样点的光照,然后再计算采样点各自的透射率,最终进行离散的积分。
每个采样点的光照比较复杂,它首先有BSDF项(f),我们一般用一个介质相关的Phase Function来表达它,其次是Visibility项来描述采样点和光源直接是否有遮挡。
那么我们来进一步看下实际游戏中visibility项如何得到。
它由三部分组成,第一部分是我们熟悉的CSM。第二部分是介质的shadowmap,它的深度是ray march得到,会根据ray march的透射率拟合一个深度,再使用esm和mipmap来进行阴影的柔和和反走样。第三部分是地形的shadowmap,可以根据光照方向和地形高度图离线处理获得。
我们为什么要加入一个单独计算的shadow mask volume呢?
原因是阴影的计算由于欠采样会有很多瑕疵。如图中红圈所示,它对于最终画面效果影响很大,所以我们考虑使用temporal的时序累积来提升阴影采样效率,以及稳定阴影数值。
而加入了temporal之后的效果,可以看到即使是动态的变化,画面也会非常稳定。
对于光源的光照计算,我们会使用Zbin的数据结构来缓存多个光源的可见性信息,进行光源的剔除,然后计算当前采样点的光照颜色,以及读取当前采样点的Visibility可见性信息。
为了画面的稳定,我们会使用temporal来累积时序上的光照结果。近似提升采样率。
但是有一个比较严重的问题,不同于Visibility的低频信息,光照是一个高频信息,对于全动态的光源,时序上的累积会造成两个BUG——Ghosting现象和拖尾现象。
Ghosting现象很好理解,当光源移动时,对于之前体素的光照累积会造成光照的拖影,这对于手电筒,探照灯等动态光源会有很明显的瑕疵。
第二个问题是相机变化时的拖尾现象,在参与性介质各向异性属性特别强时会出现。如图所示,当相机转换视角时,光照变化非常剧烈,此时再进行时序累积,会造成拖影。
但是如果我们因此不进行光照时序积累,会使体素光照欠采样产生噪点与画面抖动。
所以我们需要采取更精准的方法来处理全动态局部光源,对于一条穿过光源的视线可以和光源原点构成一个平面三角形,针对其中一个三角形,顺时针改变视线方向来进行离线预计算,一共使用96种相机方向。
这样每个平面三角形会生成一个3D纹理,然后我们根据角度将整个光源分为15个平面三角形,又由于对称性,只需要记录8个。在光照的时候,我们根据视线与光源的交点计算3D纹理坐标,进而进行采样预积分的光照进行合成。
最后一部分是重建合成最终的光照。为了性能考虑,我们会在1/16分辨率上进行光照计算,每四帧顺时针旋转发射光线的像素点。
第一帧发射最左上角的像素,第二帧发射右上角的像素。以此类推,在重建1/4分辨率尺寸的光照时,绿色的像素点是当前计算的像素点,红色的区域是我们需要重建的像素点。
假设图示的白色像素需要重建,我们考虑它周围3x3的绿色有效像素进行加权插值。
当然我们要考虑深度的不连续性,防止前景和背景错误融合。如图示的白色像素需要进行重建,首先我们会选择出周围的3x3像素,然后根据当前像素的深度去进一步过滤深度相似的像素。
比较深度会引入一个问题,就是我们之前说每四帧顺时针旋转像素,会造成重建时3x3的像素深度都是前景或者背景,所以我们需要使用特殊pattern来获取最小和最大的深度范围。
这张图是重建前的1/16分辨率的光照结果,这是重建完成1/4分辨率的光照结果。直接将1/4分辨率放大为全分辨率会带来各种问题。如视频所示,前景背景动态变化的地带,比如树叶间隙会造成重建不稳定,画面闪烁。
所以我们使用一个带深度权重的滤波核,对其进行双边滤波,并且使用噪点来扰动采样位置,并依赖后期的TAA来进行时序上的降噪,稳定画面。
系统性能
我们已经在天刀端游上线了第一版的大气系统,当时使用的测试配置为:
显卡:GTX2060
CPU:I7 9700
画质:高配
分辨率:1080p
地图:幽夜城
体积云:FULL Coverage
这段则是当时资料片的实机演示视频。
(部分演示)
测试多次性能取平均,最终的结果如图所示,整体耗时在14毫秒左右,相较于它对于整体画面的提升,游戏帧率的下降几乎可以忽略,总体性能以及性价比非常高。
最后,总结一下我们的大气系统,它是实时全动态的渲染系统,可以大幅度提升画面的质量与真实感。在整个时段和天气系统中可以统一的整合进配置,方便美术制作、调整参数设置以及针对不同场景天气进行管理,它的性能十分高效,对游戏的帧率损失微乎其微。
未来的改进方向,我们希望精简参数更容易达到比较好的画面效果,以及更自动化离线制作的管线。
附加内容
接下来是附加内容,我们支持全动态的时段以及天气,建模的体积云和体积雾可以根据当前的参数来程序化混合,任意变换各种形态以及光照效果。这个视频简单的展示了从中午到傍晚,从晴天到下雨起雾的无缝动态过渡切换的过程,暴雨天气下雨水打到物体上会自动程序化生成水雾。
我们复用了下雨潮湿效果的OcclusionMap,以及ESM深度比较来自动化实现,视频展示了雨雾开和关的效果对比。
半透雾效是一个比较棘手的问题,如水流这种半透物体的受雾特别容易穿帮,在浓雾中,我们希望玻璃这种类似材质可以完全隐蔽在雾中。
一个折中的解决方法是在RayMarch的pass单独输出一个Fogmap,对它进行mipmap chain的生成以及高斯模糊。
为了性能考虑这张fogmap只有32x32像素,alpha通道存储介质的透射率深度,我们最终雾效合成的代码如下,他并不是基于物理只是一种近似。
对比玻璃材质的面罩在浓雾中逐渐消失的过程,可以看到右边对比左边有了显著的提升,而且几乎不会占用什么性能资源。
美术在游戏中需要手动控制室外室外不同的介质浓度,美术会制作房子的简模作为包围盒,用于GI等系统。
在体积雾中我们复用这些包围盒,程序会自动的把它们体素化进Froxel,注入介质的时候根据结果选择属性,这个视频演示了单独室内体积雾浓度变强的过程,可以分别调整各自的属性。好,我的分享到此结束,谢谢大家!
Q&A环节:
Q:体积云和体积雾的光照有什么区别?
鲍鹏:主要有以下几点区别:第一点它们支持的动态光源类型不同,体积云支持太阳光、闪电,体积物支持太阳光、局部光源以及GI,另外体积云的
可见性计算只有云层的体积阴影,但是会有多个Phase Function来模拟多层闪射。
Q:地表阴影覆盖距离?
鲍鹏:地表阴影根据地表高度图处理的,覆盖所有可玩区域,云层阴影的覆盖距离可以配置,边界会使用淡出效果。
Q:Froxel的划分以及rayMarch的距离?
鲍鹏:8×8像素是一个体素,深度上会有64层,ray march会根据地球大气圈的高度计算一个终点,对于布径有一个最大距离限制,如果透射率大于等于一,会提前退出。
Q:天空盒的处理?
鲍鹏:一般天空基本只有程序生成的大气闪射以及体积云雾,有时美术会需求使用天空盒贴图作为背景,我们会使用透射率以及美术可调的参数,让美术手动选择做Blender。谢谢大家!
元宇宙数字产业服务平台
下载「陀螺科技」APP,获取前沿深度元宇宙讯息
110777025(手游交流群)
108587679(求职招聘群)
228523944(手游运营群)
128609517(手游发行群)