基于物理的体积光实现

序言
在github上Unity有一个VolumetricLighting的实现,效果大体还可以,其实却有好多细节没有实现。
阅读了一阵 VolumetricLighting的 源码,发现它只支持延迟渲染,不能用于移动平台,除了AreaLight其它类型Light没有实现阴影体积,而且光源居然不会被物体挡住,永远在最前面的缺陷!代码里留着好多TODO,其中Compute Shader计算部分和阴影级联的采样也并非按照以下文献实现,再看看更新日期,似乎这个只是做了个Demo参考。结论:移动平台无法直接使用。
但没关系,Demo里给了Unity中实现的思路,并且注释中说明了参考的文献,于是我参阅了其中之一文献,这篇文献可是来自大作《信条刺客4》的技术方案啊,于是作了下文笔记,等迟些再在Unity上也实现兼容前向渲染并移植到移动平台的实现吧。

Unity Demo地址: Unity 体积光 Demo
本文参考文献地址:https://bartwronski.com/publications/

在这里插入图片描述

正文

在这里插入图片描述

那么,为什么我们需要大气散射模拟呢?因为它是造成可见世界许多视觉元素的物理效应,如天空颜色,雾,云,上帝射线,光轴,体积阴影,甚至烟雾。

大气散射:
在这里插入图片描述
光辐射中:入射光=穿透光+吸收光+散射光

大气散射现象是在任何介质传输中由光子和粒子的交互。当光线穿过任何截至不能避免,光线可能与这种介质中的粒子发生碰撞,当发生碰撞,它们可能同时发生漫反射或吸收。我们可以定义以下参数处理:
1、穿透
2、散射
3、吸收
在这里插入图片描述
没有光线散射/介质中没有粒子:
我们假设传输介质的类似真空,没有辐射损失,或者光线在传输中在物体间弹射被增强。

典型的渲染场景–光源中的灯光从一个对象根据表面BRDF函数反弹到另一个对象,最终到达摄像机/眼睛。最简单的情况是没有反射光/GI,只是直接照明。
在这里插入图片描述
在光线传输中,当媒介中的粒子足够大,那么将影响到光线传输方程式。例如尘埃或者水的粒子使光线反弹到随机方向,使一些光线(In-scattering)进入光线传播路线,也有一些光线(Out-scattering)被反弹离开当前光线路线,变得越来越暗。
显然这个在现实中非常复杂,因为每一个粒子都是同时分散(Out-scatters)和进入(in-scatters)的,一些光线根据相位函数。所以多条光线进入光线多次,但通场在实时渲染是我们不得不忽略多重散射。

比尔-兰伯特 定律 (Beer-Lambert Law)
在这里插入图片描述

对光散射计算非常有用的物理定律是比尔-兰伯特定律,描述入射光的衰减(散射out-scattering)。这项定律规定透射率值(通过介质传输的光与入射光的比例来自给定方向的光)。通常定义为:
在这里插入图片描述
符号Be是衰减系数,定义为散射和吸收之和系数。从比尔-兰伯特定律我们可以看出,光的衰减在给定介质中传播是基于距离指数性的。

图:不同的散射类型
在这里插入图片描述
取决于介质粒子,参与作用的光线量是不同的。例如散射模型Rayleigh散射,它散射非常小的粒子,如空气粒子中,形成蓝色天空的效果。这是很有同向性、均匀的,但波长对于散射比较短的波长更强,并且吸收被忽略。
另一方面,Mie散射适合较大的粒子,如水雾和灰尘,有很强的各异向性,具有更强的向前和更高的吸收比例。

图:Phase函数
在这里插入图片描述

Phase函数是描述光在传播中在各个方向上散射的概率分布函数。它是求光向量和输出方向向量之间的角度函数。它有能量守恒性质,所有方向上的积分必须等于1(或低于1,如果包含光吸收)。一些Phase函数可能非常复杂,它们从各种数据模型中提取数据。

图:分析相位函数
在这里插入图片描述
分析相位函数
Heny-Greenstein相位函数
1、可变的各异向性因子。
2、较少的计算消耗分析光线(大部分预先计算)。
3、球谐函数展开
4、纬向球谐函数(1,g,g2,g3)

大部分相位函数类似Mie-like各异向性散射是Heny-Greenstein相位函数。它们在运行时有很多优点,例如简单地扩展到球谐函数并支持各异向性系数。

图:散射各异向性
在这里插入图片描述

公式描述到这里,接下来算法描述

体积雾

在这里插入图片描述

图:算法概览
在这里插入图片描述
算法概览
1、 体积纹理作为中间存储
2、使用计算着色器和UAV(Unordered Access Views无序访问视图)进行raymarch和高效写入

	为了有效地分割pass并分别运行,我们使用了体积纹理作为中间和部分结果的存储。
	我们使用了compute shader和3D纹理UAV在两者中写入了体积数据高效且非常方便的方式。

图:算法概述
在这里插入图片描述
图:算法概述
我们算法的关键思想是解耦和并行化典型的光线raymarching步骤算法并分别运行它们。
这样,我们可以实现并行性,并可以交换和调整每个算法的元素。

图:算法概述
在这里插入图片描述
算法概述:
我们可以在该图上看到如何将算法分多次进行。
1.首先也是最重要的部分是对于每个体积块单元执行照明和着色计算。
2.在并行(相同或并行pass)或串行中,我们估算并使媒介密度动态化。
3.然后使用存储在体纹理中的此信息,在整个体块中进行光线2D raymarching,并将结果存储在体积切片中。
4.最后使用像素着色器,在屏幕上应用信息以着色对象。

在这里插入图片描述
我们的体积雾中间存储纹理看起来像什么?

最初,我们尝试了最简单的数据布局-与世界空间坐标对齐的立方体。 它提供了多种好处–例如,非常容易的时间过滤,但raymarching通过这样的体积需要多重采样,速度很慢,并且会产生混叠伪像。

我们决定使用与摄像机视锥对齐的布局。 我们使用宽度x高度轴上的设备归一化坐标(NDC)将视锥直接映射到长方体,对于深度切片,我们使用指数深度分布。

我们尝试了各种深度分布,最后在相机附近集中了一个深度分布,这是我们需要最精确的位置,并且容易出现锯齿失真的地方。

使我们的纹理与视锥体对齐具有多个缺点,例如易于出现时间混叠和闪烁,但是光线raymarching只是对深度切片的并行扫描。

我们根据平台使用大小为160x90x64或160x90x128的体积块尺寸。 它提供几乎所有通道的固定成本,而不取决于屏幕分辨率。

在160x90x64布局中,纹理像素的数量等于720p表面的纹理像素的数量-但是对于每个单元,我们只执行一次照明计算。

效果范围取决于艺术家定义的设置,但我们的距离在50至128米之间–使长距离雾保持与当前的现代艺术方向一致,但没有理由不能进行更长距离的雾(使用指数深度分布或级联方法)

在这里插入图片描述

这个体积块纹理分辨率足够了吗?
图:体积块分辨率–太低?
在这里插入图片描述
体积块分辨率—太低?
1、 我们存储整个射线的信息
2、 以及它的每个深度-tex3D过滤
3、 每1080p像素可获得正确的信息
4、 没有边缘伪影
5、 劣势-效果柔和

体积块纹理的分辨率看起来很低,但这是足够的:
1.我们存储沿射线存储的每个深度的低频信息。
2.由于在应用效果时,我们对体积数据使用四线性滤波,因此无法看到体积纹理的单个纹理像素。
3.每个目标像素都会从其内部分辨率接收有关正确和精确深度的信息。
4.透视校正和体积形状可确保正确分发信息。
5.我们没有得到深度不连续的边缘伪像(在下一张幻灯片中进行介绍)。

显然,所产生的效果非常柔和,并且缺少高频几何细节,但它符合我们的艺术方向,并且与现实非常相似(因为在真实大气中会发生多重散射效果,从而使光轴的外观变得柔和
很多)。

不依赖场景深度的最终优势是可以在阴影贴图准备就绪后立即与常规场景渲染并行地计算此过程,例如在具有控制台API或Mantle的AMD硬件上使用异步计算。

图:2D解决方案-边缘神器
在这里插入图片描述
在进行常规2D低分辨率渲染时,主要问题是边缘不连续性上的行为。 对于低分辨率后处理计算,我们必须从许多潜在的深度片段中选择一个特定的深度-这样,某些最终阴影片段将具有不正确的信息-可以是插值的,也可以是从邻域中选择的(双向上采样)。

图:3D纹理—正确的边缘
在这里插入图片描述
幸运的是,有了3D纹理和3D插值,我们就不会遇到这样的问题。
通过线性插值计算函数,每个完整分辨率的片段及其深度都会得到适当的分段线性插值。
尽管它仍然处于低分辨率状态,并且可能会“锯齿状”,但它没有边缘不连续的瑕疵。

图:锯齿问题
在这里插入图片描述
我们可以在该图上看到低通滤波如何消除高频源信息的时间混叠/闪烁问题。

图:锯齿问题
在这里插入图片描述
锯齿问题
4个阴影投射 1536x1536
1、 太多细节
2、 超过Nyquist频率的阴影
3、 大量的锯齿,闪烁
4、 需要使用低通滤波器
5、 纯粹的32-tap PCF =令人无法接受的性能

我们算法的第一步是准备阴影映射图,以用于雾计算太阳散射阴影。
为什么我们需要它?
我们的常规阴影级联具有很高的分辨率(4个级联,1536x1536或1k x 1k,具体取决于平台)。
对于我们来说,这是太多细节了,尤其是对于近距离和在最初的两级级联中凝结的最初几米。
为了获得平滑和近似的体积雾,我们需要使用分辨率更小的东西,以减少因移动树叶等引起的任何闪烁/混叠伪影。
使用宽内核PCF的第一个实现的性能非常差,并且仍然存在一些闪烁和混叠现象。

图:指数级阴影映射图
在这里插入图片描述
指数级阴影映射图

该解决方案来自指数阴影贴图算法。
这是一种简单的算法,可以过滤估计的阴影概率,因此可以对阴影函数进行下采样。
计算阴影测试非常简单而且非常有效。 ESM的一些简单代码段在下文展示。

在这里插入图片描述
指数级阴影映射图
1、可以过滤(可分离模糊)
2、 有一个缺点-影子泄漏
3、 媒介影响甚微
4、 代码片段在下文展示

首先,我们将级联的阴影贴图下采样四次(目标R32F 1024 x 256纹理)。 在向下采样期间,我们计算指数阴影函数(请参见指数阴影贴图,使用以下方法对方差阴影贴图的扩展指数函数而不是Chebyshevs不等式)

在这个过程中,我们还做了额外的箱型模糊过滤器(作为两个单独的步骤),使阴影更柔和,并消除锯齿伪影。
ESM的一个缺点使其不适用于常规渲染—阴影泄漏—在我们关心的媒介中并不明显。

图:点光源锯齿
在这里插入图片描述
图:算法细节
在这里插入图片描述
因此,算法的第一步是对阴影信息进行向下采样。

图:算法详细
在这里插入图片描述
当我们准备好向下采样的阴影信息后,我们可以继续参与中间照明计算。

图:密度估算和体积照明
在这里插入图片描述
密度估算和体积照明

参与媒体密度估算

-程序生成柏林噪音模拟风
-垂直衰减
-散射系数保存在体积纹理A通道

散射照明

-ESM着色主光源
-环境项为常量
-点光源循环
-存储在体积纹理rgb通道中

我们将密度和光照计算结合起来,这是因为带宽占用更小,分辨率也一样,但它们可以分开并完全解耦。
密度计算是相当直接的,它只是柏林噪声的一个倍频程的动画风。我们尝试使用多个倍频程,但最终的差别是相当微妙的增加成本。
我们还计算了垂直介质密度衰减,因为通常重粒子,如蒸汽-水粒子,倾向于聚集在地面附近,呈指数分布。
散射系数存储在体纹理的A通道中。

对于照明部分,我们简单地从主光(太阳/月光)、恒定的环境项和多个与视锥相交并被艺术家标记为影响大气的光的多个动态点光源来积累照明。
我们使用上面提到的指数阴影贴图阴影技术来获得主光的阴影。
在体纹理RGB通道中存储了密度调制的光照信息。

图:密度估算和体积照明
在这里插入图片描述
密度估算和体积照明

AC4中的光照散射相位函数

-不是基于物理(而是艺术驱动)-2种颜色(太阳方向,相反方向)

在AC4上,我们没有任何基于物理的相位函数。灯光颜色梯度(朝向太阳的方向)是纯粹的艺术驱动。
我们有两种颜色的相位函数-在太阳方向和相反的方向,除了完全各向同性的形状。
显然,任何相位函数都可以在这一过程中应用,以达到更物理上的校正效果。(稍后描述)

图:算法详细
在这里插入图片描述
第三步是通过线性遍历雾量并执行数值演算解来求解散射方程。

图:求解散射方程
在这里插入图片描述
求解散射方程
1、 通过体积块Raymarching
2、 累积消光系数
3、 应用Beer-Lambert定律计算Alpha中存储的透射率
4、 累积散布在RGB通道中的光
5、 将透射率也应用于散射

那么如何解决这个散射方程呢?
用Beer-Lambert定律描述外散射效应,即给定距离下密度积分的指数衰减函数。
对于散射而言,到目前为止是散射光的简单总和(考虑到基于散射的距离)。
我们已经在体积纹理中计算并累积了散射值。
因此,对于每条射线,我们都可以简单地从摄影机开始穿过体积,计算出密度总和,累积散射内辐射和散射外衰减因子

图:求解散射方程
在这里插入图片描述
求解散射方程
1、 2D compute shader
2、 蛮力,数值积分
3、 穿越深度切片并积累
4、 使用UAV写入

我们的compute着色器在每个步骤中都会累积散射光和雾的密度(数值解)。
这样,我们最终得到了一个体积纹理,在每个纹理像素上都有信息,从照相机到给定3D点的入射光的数量是多少,以及我们积累的参与介质的密度是多少(描述了入射光的向外散射的数量)。

准备好以前向(直接在绘制对象时)或延迟(以全屏四通道)的方式应用此数据。

图:求解散射方程
在这里插入图片描述
在此图上,我们看到2D计算着色器线程组如何遍历3D体积,累积散射光和消光系数。

图:为什么不平行求和?
在这里插入图片描述
为什么不平行求和?
1、 尝试了这种方法!
2、 并行前缀和(扫描)
3、 …但比强行求和慢了20-30%!
-LDS 堆积冲突
-着色器占用率更差
-本地缓存垃圾
不过,在某些情况下2D可能会更好。

图:应用效果
在这里插入图片描述
应用效果
多层不透明和透明对象以及双线性3D tex过滤。

我们可以在此图上看到,由于存储了整个视线信息,我们如何可以将体积雾信息应用于实体(1)和多个透明对象(2)和(3)。 它们都具有适当且经过过滤的透射率和散射信息。

图:应用效果
在这里插入图片描述
应用效果
1、 计算像素体积纹理位置
2、 查找和采样3D纹理
3、 在前向和延迟中待定要做的事情
4、 …兼容任何数量的透明层

在屏幕上的最终应用效果在前向和延迟渲染中都是纯粹的-没有特殊的技巧,只是从3D纹理简单的双线性查找和一个融合的乘法-加法操作。
值得注意的是,该效果与任何数量的透明对象层兼容。
在这里插入图片描述

后续

图:性能
在这里插入图片描述
总成本惊人的小,大约1.1ms。用双分辨率计算它的成本是1.6ms。

最昂贵的部分是构建密度和采光体积块,约为0.43ms,剩余时间均在0.2ms以下,但“应用”pass可以与灯光结合,变为“免费”。

图:优化
在这里插入图片描述
优化
1、 分割pass以保持寄存器计数低水平
2、 低分辨率ESM超高效
3、 对粒子和半透明对象重复使用ESM阴影贴图
4、 将体积雾照明体积用于照明和阴影粒子
5、 延迟照明中结合雾使用

首先,体积雾是我们从高波占有率和低VGPR计数中获益最多的一种效果-它有助于分割各种通道。

体积雾的一些成本实际上是阴影贴图设置的成本-将其重新用于其他低频照明非常有意义,例如粒子、半透明和透明对象(海洋等)。

此外,计算纹理中的照明和在多个灯光上循环的成本也可以被这种渲染过程重用—通过简单的体积纹理查找。

最后,没有必要计算雾距离非常大的全照明-在一定距离的阴影和照明可以隐藏。

图:继续优化
在这里插入图片描述
继续优化
1、 在G缓冲区渲染期间使用异步计算执行雾
或者
2、 如果有平铺MaxZ,请使用它进行消隐(在照明/Raymarching提前退出)
3、 使用聚集/平铺的本地光照。

其他一些优化包括对任何实际渲染几何体后面的部分体积纹理执行早期操作。如果引擎有某种形式的分层Z缓冲区可用,这些值可以通过体积雾读取并用于早期输出。
同样,也不需要在所有可见光上循环——像正向+或聚集着色这样的解决方案可以用于雾光消隐。

如果引擎没有此类信息,对于以下大场景那么性能就不会从中受益多少。例如很多户外的远程场景。可以使用下一代控制台对体积雾进行异步计算计算。因为没有剔除的算法并不依赖于场景几何体一旦阴影图准备好就可以计算出来。体积雾可以相当带宽和ALU都很重,所以把它和一些顶点重传类填充G缓冲区。

图:延迟类光照补充
在这里插入图片描述
以类似的方式,照明也可以注入体积作为替代基于前向照明技术。我们可以计算光的体积边界盒,并且只对单元进行照明与之相交。这可能是非常容易和完成使用间接调度DX11/下一代控制台上的功能。此优化对于使用许多VGRP的非常复杂的灯光非常有用计算-就像区域灯光。

图:刺客信条4以外的补充
在这里插入图片描述
在最后的演示部分,我将描述在AC4之后开发的体积雾的扩展。

1如何使效果更加基于物理-缺少对分析光源和基于SH光源的基于物理的相位函数的支持。
2通过引入时间重投影和抖动,使效果更加稳定。

图:支持环境光、GI
在这里插入图片描述
缺少对雾环境/天空照明的支持会导致阴影区域的场景变暗太多。
有些光散开了,但没有一个是因为缺少照明而散射的,这会产生不切实际的结果。因此我们需要添加一些GI。

图:支持环境光、GI
在这里插入图片描述
在这个比较中,我们可以看到添加的天空照明如何改善结果并在阴影区域添加适当的雾。

图:夸张的GI
在这里插入图片描述
最后,我们可以看到这个截图(夸张的雾和GI)的GI颜色有助于最终雾颜色,添加适当的颜色空间变化。

图:支持环境光、GI
在这里插入图片描述
支持环境光、GI

Henyey-Greenstein相函数的分区SH的计算

-按视图方向旋转
-第二队列SH等于
Float4(1.0f, dir.y, dir.z, dir.x) * float4(1.0f, g, g, g);

计算给定点环境照明的SH积积分和相位函数

  • …每个颜色通道的单点投影

环境照明或GI的一个非常常见的照明储存基础是球谐函数。
它们的数学性质是多重的(正交性、旋转不变性等),但另一个是易用性
纬向球面谐波。
它们可以被简单地旋转(特别是对于低阶的SH),这对于体积雾也是。
HG相函数对纬向SH有很小的展开式,对于二阶,它很容易实现旋转(幻灯片上的代码)。
然后要计算体积雾响应,只需简单地计算SH乘积旋转Phase函数SH展开的视矢量积分与天空光存储/GI SH代表。

在这里插入图片描述
1、 仍可能出现欠采样和混叠
2、 使用时间抖动和重投影
3、 常用现代AA技术(泪滴2/3,杀戮空间,刺客信条4,虚幻4)
4、 比二维的简单多了

而我们花了相当多的时间去消除混叠和欠采样用下采样SM表现的体雾问题,仍然存在
可能是一些采样不足的伪影。
解决这个问题的方法之一是通过抖动样本来使用时间超级采样对每一帧进行模式化,并使用时间平滑和重投影相结合这些图案来自多个帧。
在越来越多的游戏中,它被用作一种常见的AA技术,但也可以用于体积雾以及-以及重投影在三维比在二维情况下容易得多。

图:2D重影问题
在这里插入图片描述
左:第n帧
右:第n+1帧,未知像素重建不当

在2D中,重投影通常是有问题的,因为当场景中的对象移动时,会出现遮挡/分离问题。很难同时检测到这种移动(尤其是在抖动时),也很难找到合适的数据来填补由此产生的漏洞。
因此,通常算法很难在动态对象的潜在抖动和模糊/跟踪/重影之间找到一个最佳点。

图:体积块重影
在这里插入图片描述
体积块重影
左:正确存储数据
右:无效重影
在3D中,这要容易得多。虽然动态对象在移动后所占用的空间仍然无效,但其后面的所有数据都是正确的(如果不使用任何早期筛选优化)。只有在体积块外部不与视锥对齐的数据无效。
(但在强视相关和各向异性相函数的情况下,可能会出现明显的重投影问题)。

图:抖动采样
在这里插入图片描述
抖动采样:
左:常规网格采样
右:抖动网格采样
1、 用混叠代替噪声
2、 用混叠代替噪声
3、 可能在时域抖动和滤波

另一种消除混叠的技术是空间网格中的样本抖动。虽然常规的网格采样会产生难以抵抗的混叠(因为在源信号中不可见的低频分量出现),但是抖动的网格采样用混叠来交换高频噪声。
使用低分辨率的核函数可以很容易地过滤和模糊噪声和高频信号。
此外,可以在时间域和空间域中抖动和滤波。

图:子单元抖动/超采样
在这里插入图片描述
子单元抖动/超采样
这两个屏幕截图显示了仅仅通过最简单的1个采样时间抖动和重投影就可以获得更好的结果–几乎所有的边缘伪影都消失了!
我们没有检查任何复杂的三维抖动网格采样模式,但它值得在将来研究,因为结果可能会更好。

代码片段展示部分:

图:指数阴影映射图用于体积块
在这里插入图片描述
指数阴影映射图用于体积块
1阴影贴图下采样/转换到指数空间
2可分离的11像素宽的箱型滤波器(2个普通pass)
3应用阴影映射图

图:解散射方程
在这里插入图片描述
强行raymarching

图:解散射方程
在这里插入图片描述
解散射方程
应用Beer-Lambert定律
散射方程的一步迭代数值解法
写出最终散射值

猜你喜欢

转载自blog.csdn.net/u012740992/article/details/110147854
今日推荐