2021年11月22日-24日,由腾讯游戏学堂举办的第五届腾讯游戏开发者大会(Tencent Game Developers Conference,简称TGDC)在线上举行。本届大会以“Five by Five”为主题,邀请了海内外40多位行业嘉宾,从主论坛、产品、技术、艺术、独立游戏、市场及游戏社会价值7大专场共同探讨游戏产业趋势和多元价值,以开发者视角与需求为出发点,助力游戏行业良性发展,探索游戏的更多可能性。

一、概述

我分享的是《天涯明月刀》端游自研引擎的体积云和体积雾的渲染系统开发和优化。首先给大家介绍一下背景以及目标:天刀在业内一直以动态的云海体积云著称,算是最早一批应用体渲染的大型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中根据力场生成带扰动的风场,来驱动这些介质,用两套实时计算的偏移场来混合介质的位移。

三、渲染

渲染分成4个部分,首先是Froxel的渲染。Froxel是Frustum+voxel的缩写,从字面上就可以知道,它是将相机的视锥分割为一个一个的3D体素,我们之前说了,使用froxel渲染的是200m以内的视锥。

打开网易新闻 查看精彩图片

视锥分割成了体素后,第一步我们会对每个体素注入介质的属性,同时我们对每个体素生成shadow mask来保存它们的可见性信息。第二步计算每个体素的光照结果,第三步将每个体素的光照结果沿相机视线方向进行离散化积分,得到最终的光照结果。

打开网易新闻 查看精彩图片

这个视频展示了近处200mFroxel的渲染结果,太阳光是从左上角打下来,场景中有多个局部动态光源,这里的体积雾的建模会根据风力随机扰动属性值。

接下来是RayMarch的介质渲染,如图所示,我们会在Froxel后发射光线来继续渲染之后的参与性介质。首先我们需要计算光线的起点和终点,然后扰动起点位置来防止一些ray march的pattern,红色箭头指向的小图中,每个红点代表raymarch的采样位置,可以看到由于起点扰动,每个采样点并不是规则的齐步步进。

打开网易新闻 查看精彩图片

一开始我们会使用比较长的步长来快速步进没有介质的区域,当我们采样到介质时,如图所示,我们会多次试探来确定一个比较好的步长,我们在采样到介质之后,会每次减半步长来回退,如果回退还在介质中则继续减半步长回退,直至离开介质,然后再次步进介质中,如此循环直至回退数达到设置的最大值。

打开网易新闻 查看精彩图片

在介质中我们会逐渐加大步进步长,当离开介质后,我们会重新使用更大的步长来步进。

打开网易新闻 查看精彩图片

下面我们详细讨论下光照模型,我们计算相机通过介质看到的最终颜色,首先要知道背景颜色,就是图中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的像素深度都是前景或者背景,如图所示,这9个采样点全是背景没有前景,所以我们需要使用特殊pattern来获取最小和最大的深度范围。

打开网易新闻 查看精彩图片

这张图是重建前的1/16分辨率的光照结果

打开网易新闻 查看精彩图片

这是重建完成1/4分辨率的光照结果。直接将1/4分辨率放大为全分辨率会带来各种问题。

打开网易新闻 查看精彩图片

在前景背景动态变化的地带,比如树叶间隙会造成重建不稳定,画面闪烁.

打开网易新闻 查看精彩图片

所以我们使用一个带深度权重的滤波核,对其进行双边滤波,并且使用噪点来扰动采样位置,并依赖后期的TAA来进行时序上的降噪,稳定画面。

打开网易新闻 查看精彩图片

四、性能统计

我们已经在天刀端游上线了第一版的大气系统,当时使用的测试条件如图所示。

打开网易新闻 查看精彩图片

测试多次性能取平均,最终的结果如图所示:整体耗时在1.4毫秒左右,相较于它对于整体画面的提升,游戏帧率的下降几乎可以忽略,总体性能以及性价比非常高。

打开网易新闻 查看精彩图片

最后,总结一下我们的大气系统,它是实时全动态的渲染系统,可以大幅度提升画面的质量与真实感,在整个时段和天气系统中可以统一的整合进配置,方便美术制作、调整参数设置以及针对不同场景天气进行管理,它的性能十分高效,对游戏的帧率损失微乎其微。

打开网易新闻 查看精彩图片

未来的改进方向,我们希望精简参数更容易达到比较好的画面效果,以及更自动化离线制作的管线。

打开网易新闻 查看精彩图片

Bonus

此外,我们还持全动态的时段以及天气,建模的体积云和体积雾可以根据当前的参数来程序化混合,任意变换各种形态以及光照效果。

打开网易新闻 查看精彩图片

这个视频简单的展示了从中午到傍晚,从晴天到下雨起雾的无缝动态过渡切换的过程,暴雨天气下雨水打到物体上会自动程序化生成水雾,

我们复用了下雨潮湿效果的OcclusionMap,以及ESM深度比较来自动化实现,视频展示了雨雾开和关的效果对比

半透雾效是一个比较棘手的问题,如红圈所示,水流这种半透物体的受雾特别容易穿帮,在浓雾中,我们希望玻璃这种类似材质可以完全隐蔽在雾中。

打开网易新闻 查看精彩图片

一个折中的解决方法是在RayMarch的pass,单独输出一个Fogmap,对它进行mipmap chain的生成以及高斯模糊,为了性能考虑这张fogmap只有32x32像素,alpha通道存储介质的透射率深度,我们最终雾效合成的代码如下,他并不是基于物理只是一种近似。

打开网易新闻 查看精彩图片

美术在游戏中需要手动控制室外室外不同的介质浓度,美术会制作房子的简模作为包围盒,用于GI等系统。在体积雾中我们复用这些包围盒,程序会自动的把它们体素化进Froxel,注入介质的时候根据结果选择属性。

打开网易新闻 查看精彩图片

Q & A

Q:体积云和体积雾的光照有什么区别?

A:主要有以下几点区别:第一点它们支持的动态光源类型不同,体积云支持太阳光、闪电,体积物支持太阳光、局部光源以及GI,另外体积云的可见性计算只有云层的体积阴影,但是会有多个Phase Function来模拟多层闪射。

Q:比表阴影覆盖距离?

A:地表阴影根据地表高度图处理的覆盖所有可玩区域,云层阴影的覆盖距离可以配置,边界会使用淡出效果。

Q:Froxel的划分以及rayMarch的距离?

A:8×8像素是一个体素,深度上会有64层,ray march会根据地球大气圈的高度计算一个终点,对于布径有一个最大距离限制,如果透射率大于等于一,会提前退出。

Q:天空盒的处理?

A:一般天空基本只有程序生成的大气闪射以及体积云雾,有时美术会需求使用天空盒贴图作为背景,我们会使用透射率以及美术可调的参数,让美术手动选择做Blender。