上一篇 中主要介绍了 定制化噪声纹理 在体积云建模中的应用。这次粗读这篇分享的下半部分,即体积云的——光照、渲染、优化几个部分。 其中光照之所以从渲染中拆出来,可以认为光照更多的是做物理模型计算上的分析,渲染则更多考虑实际游戏中的实现。
文章还是以翻译原文的讲稿为主,重点部分会摘一些原文。由于篇幅原因会分为上下两篇,这是其中的下篇,打星号的部分则是我个人的补充说明。
云的光照是一个计算机图形学中研究很深的领域。其中最好的结果需要基于大数量的采样。
在游戏中,当你询问云渲染方面的性能预算时,往往得到的答案是“零”。我们决定先实验当前主要的基于近似计算的渲染技术,以再现3个最重要的光照特性。
前两者有标准的方案,但是第三条需要我们自己找方案。
当光线穿过云层时,主要光线大部分时间都在水滴和冰晶之中多次折射后才进入人眼。
当光线最终穿出云层,被称为 外散射 ;或被云层 吸收 ;或与其它光线合并,则被称为 内散射 。
在电影视效工业中,我们可以花很多时间来收集光照信息并粗略地再现这个效果(离线渲染),但是在游戏中我们必须用一些近似方案。有一种把光线的这3方面行为作概率估算的标准方法。
贝尔定律表明,我们可以基于一个点所处介质的光学厚度来计算出射光线的量——这样我们就有了描述云中一个点光线量的基本方式。
如果我们用能量代替透射率,用深度代替厚度,并绘制出如图所示的曲线——你能看到能量随深度程指数减少。这个函数构成了我们光照模型的基础。
*这里的贝尔定律应该对应的是光学上常说的Beer-Lambert Law,可以简单概括为:光被透明介质吸收的比例与入射光的强度无关,与介质的厚度有关。
不过在一个点的光能计算上还需要考虑另一个组成部分的贡献,即光线向前或向后的 散射概率 。这一项被用来产生“一线亮光”的视觉效果。
在云层中,向前散射的概率是更高的——这被称为 各向异性散射 。
在1941年,Henyey-Greenstein提出了这一模型以帮助天文学家计算银河系尺度的光学(galactic scales),不过今天它被用来较好的再现云层光照的效果。
该模型的 g 值为必须在(-1, 1)范围内。 g 的负值对应于背向散射,正值则对应于前向散射。 g 越大,在沿入射方向附近发生的散射就越多(分别对应背向散射和前向散射)。
每次我们采样计算光能量时,要乘上Henyey-Greenstein相函数。
*左侧是基于贝尔函数(厚度)计算的结果,右侧是两者考虑计算的结果。
但到这一步我们仍遗漏了一些重要的特性——云层的暗色边缘。这是一个还未被很好的提取成论文方案的特性,因此我们需要自己探索并理解发生了什么。
通常在电影工业中,我们会对一个点采样尽量多的光照并使用一个复杂得多的相函数(phase function)。通过这种暴力的方式也能得到这个结果(原文提到了Magnus Wrenninge的多重散射分享),不过在游戏中我们必须找到一种近似方案。
我的一个以前的同事Matt Wilson说一堆粉末状的盐能得到一个类似的效果,因此我把这个作为研究的方向。
*这几个特性都是基于光传播的估计,由于每一项都做了一定的简化而损失了部分没考虑的能量,严格来说最后的结果是差能量的;但是由于得到差不多的视觉效果即可,因此是可行的。
当你理解了这个效果(边缘更暗),就会处处留意到它,而不会视而不见。即使在稀疏的云层也有这一特性,它的暗色渐变会更宽一些。
现有(前面的贝尔+HG)光照公式无法直接实现这个效果的原因是,我们用的光传播函数也是一个估算,因此没把这类项列入考虑。
让我们把这一效果作为基于深度的统计概率问题来考虑。
当光线深入云层,潜在的散射概率会提高,会有更多光线进入我们的眼睛。
把图中两个函数组合起来(Bear's Law和Powder)就得到了这一效果的描述方式。
我仍在美国计算机协会数字图书馆(ACM digital library)寻找 Beer’s-Powder这类近似方案,目前还没在其中找到有类似名字的方法(意思是这个trick可能是分享人首创的)。
*图中展示了视觉对比,其中贝尔定律负责基础散射部分,粉末效果函数负责暗色边缘部分,最终方案是两者的组合。
在游戏中,这一效果为厚的云层增加了很多真实感,帮助我们突出了场景的部分轮廓。
不过必须记住这个效果是一个视点相关的效果。我们 只应该在视觉向量(摄像机方向)与光照向量接近时看到这一效果 ,因此粉末函数需要内含基于这一点(向量夹角)的渐变过程。
*原分享中这里有一段视频展示了摄像机转动过程中云的视觉变化。
我们光照模型的最后一部分是——对于 积雨云 我们人为的增加了它对于光的吸收系数(显得更暗)。
*这部分主要探索了云中一个给定位置的点如何计算光照,后面则是基于这个结论出发做光线步进采样的过程。
前面几个部分已经介绍了云的建模与光照算法。这一节开始会描述如何在一帧中进行采样并渲染云层,以及如何将云与大气和每日时间系统集成。
通过光线步进(ray march)开始渲染的第一步是决定起始位置。在我们的情况中,《地平线》中的主要视点在地球表面上(而地球是球形的)。
构成我们游戏中地球外层的大气包围层的云,分别处于大气中的不同层级。
如果从海平面看向远方,你能清晰地看到地面的曲率,以及云层下降到地平线以下的效果。
基于这些原因我们游戏中的云基于在球状大气层中的位置被划分为两类:
通过ray marching穿过球形大气层,我们能:
*通常意义的“天空盒”实际上是没有把这个天空弧度问题纳入考虑的,这里考虑进去才能做出比较自然的“海平面”或“天边”的效果。
在实际的情况中我们不希望使用任何非必要的昂贵技术——相比采样射线方向的每一个点,我们把采样分为两个细节层级(步长不同),仅在实际命中云层时才进行细节逐步采样。
回顾之前云建模中提到的,基于低细节噪声采样来绘制云的基础形体(左图),然后采样高细节的噪声以添加写实化的细节(右图)。
其中高细节的噪声可以始终视为对云的基础形体做边缘“腐蚀”的效果。
这意味着我们在计算高细节噪声采样时,与之相关的其它项都可以通过低细节采样的结果返回一个非零值。
这能帮助构建一个包围云区域的 等值面(isosurface) 。
So, when we take samples through the atmosphere, we do these cheaper samples at a larger step size until we hit a cloud iso surface. Then we switch to full samples with the high detail noise and all of its associated instructions. To make sure that we do not miss any high res samples, we always take a step backward before switching to high detail samples.
因此,当我们穿过大气层进行射线采样时,我们采用大步长的采样直到命中一个等值面;之后我们就切换到全精度采样,采样高细节噪声和它的相关项。
为了确保我们不错过高精度采样的样本,在切换到高精度前我们始终 后退一步再开始高精度采样 。
当图像的alpha值接近1时,我们就不需要继续步进采样了。
如果始终未达到alpha值为1,我们引入了另一个优化:
在几次连续采样并返回0密度值之后,我们切换回大步长的检测,直到再次命中另一个云层表面或达到体积云层的顶部。
由于朝向地平线的射线长度更长,因此我们从64个潜在的采样数开始,最终得到大约是128个潜在采样数。之所以说是“潜在的”,因为可以通过一些优化使采样提前结束——并且我们真心希望能如此。
这就是我们如何通过采样来构建图像的alpha通道。而要计算光强度,我们需要更多的采样数。
*这里可以把采样得到的alpha值视为“视觉密度”,即对采样密度的加权累加。
通常在ray march中(采样光照)需要如图朝光源采样,基于光照方程计算能量并加总,直到退出步进过程或alpha值达到1。
在我们的方案中,我们每一步在一个 锥体 内向太阳(光源)方向做6次采样,并且通过计算相邻云层密度带入我们的光照方程中,综合权重得到一个较好的效果。
其中最后一次采样会被设置为远离其它采样点,以捕捉(capture)远距离云层投下的阴影。
*由于采样精度的原因,这里说的“阴影”最多只能是影响一些明暗变化。另外,锥体及内部的采样点的相对位置肯定是基于一些算法预先生成的。
图中可以看到仅进行alpha采样步骤后,我们的云的效果:
包含锥体内5个对光源采样的点
以及一个对远距离云采样的点
为了提升光照采样的性能,我们在图像的alpha达到0.3后切换到简易版的shader,这使整体的着色过程提高了2倍速度。
将光照采样位置的深度(在云中的)带入我们光照模型中贝尔定律的部分,并且能量值在云中会基于深度衰减,最终加总与标准的体积渲染中的ray-marching步骤一致。
我们的ray march方案的最后步骤是采样高海拔的2D云纹理。
这里展示了一组不同类型的卷云和高云的纹理集合,它们可以平铺并以不同速度和方向进行卷动(以实现动态的高海拔云层效果)。
在现实中,云中会混合不同频率(颜色)的光线来产生优美的颜色效果。而由于游戏中的世界是基于大量估算的,因此我们在计算云的颜色使必须基于一些假设。我们在计算云的颜色时考虑了以下模型(这里指计算方式的归纳):
最终,我们把环境项和直接光照项求和,并基于深度值(指远距离)逐步衰减至大气层颜色。
现在,我们可以改变一天中的时间来使光照和颜色自动变化。这意味着不需要使用预烘焙来占用稀缺的内存空间,整体的内存开销仅有2个3D纹理和1个2D纹理,而不是一堆基于billboard渲染的云和天穹的纹理。
用“廉价的”(大步长的)方式采样,直到潜在地命中云层
使用64-128个潜在的采样数,在云内部每一步做光照采样时,使用一个锥体内采样6个点的方式。
光照采样在云中一些深度后从全精度切换至“廉价版”方式
*体积渲染的核心思想就是在介质内做ray march,并逐点采样加总对光照的贡献。问题就是要平衡性能开销(采样数、步长和着色精度)和效果,这一点很难。
*之前介绍过一些方案中会借助 有向距离场 来辅助第一次射线命中定位,但那又需要额外储存空间的SDF数据。
*如果是更规则的固体,或许他们可以使用先对BVH做射线检测的方式来预判命中位置。我猜测他们没有这么做的原因是由于云形体的不规则性——如果要动态更新云的包围盒数据,其实性能上的trade off就未必划算了。
前面介绍的方案每帧需要耗时20毫秒(当时在PS4上)。
这意味着尽管效果很好,但仍然不够快,无法直接集成到游戏内。我的协作开发者以及导师 Nathan Vos提出了一种优化方案。
像素的位置需要重映射到前一帧,来确保视觉效果上是连续的(类似Motion vector思想)。
*这类基于分时降低渲染压力的思想后来也广泛运用于光线追踪降噪中(之前有文章介绍过)。
在那些无法重映射像素的点,例如屏幕边缘(摄像机移动时),我们采用低精度缓冲区的结果进行替换(后续屏幕空间精度会有部分像素重新计算,并重写入缓冲区)。
Nathan的方案使得最终在半精度时的着色都比之前快了10倍甚至更多,并且我们可以使用过滤器(filters)来提升画面精度。
这项优化是最终体积云的效果能出现在我们游戏中的重要原因。最终性能开销在2毫秒左右,并且大部分来自于大数量的渲染指令调用(而不是采样或其它)。
相比于最初的目标,我们获得了巨大的成果。并且我们仍在进行工作以致力于提升性能和可控性;我们也在继续开发大气模型和天气系统,并将在后续在网页和未来的讲座上进行分享。
SIG上的分享往往就是有很多图形学上的细节。例如这篇分享中,基本就从云的物理分析及建模方案开始,逐步介绍了建模、光照、渲染等几个部分,从建模上的合理近似到图形功能开发的过程。最后的降精度、分帧的优化现在来看反而是一个很常见的做法了。整个过程因为限制很多,因此也充满了实践智慧。
不过也能看出在2015年的时候,体积云相对还是一项昂贵的技术,即使优化到了2毫秒,(整合上其它全部渲染内容)几乎也就判定了游戏只能以30帧每秒来运行。后面我们可能还会读到一些体积云方案的优化迭代,但这个游戏中的一些基础探索都是很宝贵的。
其实在游戏中引入大幅提升画质的图形技术,在我看来从来都不是一个玩法设计导向(或制作人拍板)的事,它往往源自美术人员和开发人员的自我激发;最终的性能空间、优化、整合的效果是一个各部门协作和商议的过程,30帧的画面领先的游戏和60帧的画面平庸的游戏,很难说应该选哪个,这是一个复杂的话题(包括是否要做多种画质切换与适配)——至少在这个游戏上,他们选择做一个画面领先的游戏。
类似我的玩家可能会觉得,我不在意云有多好看,最终还是要玩得流畅——但是实际上你并不可能真的不在意,这是把你引入这个世界(入戏)的重要组成部分。最近回归魔兽世界,看到巨龙时代里和天空盒融为一体的“图片云”就特别出戏,由此我也进一步意识到这一点:当你的整个世界的渲染精度都足够了,那云也是不能不够“真”的。
评论区
共 条评论热门最新