Real-Time Global Illumination(实时全局光照)-3D Space

GI is important but complex。

从之前的经验中,我们可以通过Split Sum等经验的积累,不困难的获得很好的直接光照表达方式;而在实时的光照领域,我们寻找单次间接光反射能够解决的快速反射方法。

  • Recap GAMES101:对于非光源的表面点时,我们可以理解这一次的反射为这一表面点作为光源形成的光照点;(我们也可以把首次反射的光源处作为secondary lightning) 单次反射model

Reflective Shadow Maps(RSM)-3D Space

  • 解决想要得到某个点p,对于获取它的间接光照效果,我们需要得知那些信息?
  1. 哪里会被光源照亮?——通过Shadow Map进行查找
  2. 对于某一个被照亮的点,我们需要将所有的间接光照对这一点着色的贡献进行累加进行输出,我们以逐屏幕空间下pixel作为Patch来对p进行照亮?(原本要立体角采样,我们可以将积分切换,我们把对立体角积分转写为对light的积分;
Lo(p,ωo)=ΩpatchLi(p,ωi)V(p,ωi)fr(p,ωi,ωo)cosθidωi=ApatchLi(qp)V(p,ωi)fr(p,qp,ωo)cosθpcosθqqp2dAL_o(p, \omega_o) = \int_{\Omega_{patch}} L_i(p, \omega_i) V(p, \omega_i) f_r(p, \omega_i, \omega_o) \cos \theta_i \, d\omega_i= \int_{A_{patch}} L_i(q \to p) V(p, \omega_i) f_r(p, q \to p, \omega_o) \frac{\cos \theta_p \cos \theta_q}{\|q - p\|^2} \, dA

通过这一流程,我们总结算法流程如下: RSM 用于实时计算间接光照(一次弹射)。核心思路: 从光源方向渲染一张 Shadow Map,但每个像素不只存深度,还存世界坐标 、法线、反射的光通量 (光源直接照到该点的功率)以及这些 Shadow Map 的像素对应直接被光源照亮的表面点(称为 次级光源 qq)。 对于场景中任意一个需要着色的点 PP,我们遍历(或采样)这些 qq,累加 qq 对 PP 的间接光照贡献, 即使 qq 完全相同(同一个 patch),只要 PP 不同,以上所有几何项都会变化,所以最终的间接光照贡献也不同;下面感谢下边RSM参考中的对应Steps;

假设光源照亮了墙上的一小块区域(一个 patch),该区域均匀发光(类似一个面光源)。现在有两个不同的接收点 P1和 P2: P1P1​ 离 patch 很近,并且正对着 patch → 收到很强贡献。 P2P2​ 离 patch 很远,并且法线背对着 patch → 收到很弱贡献。 虽然它们看到的是同一个发光 patch,但结果天差地别。这就是“针对不同的 P点,即使 patch 相 同,收到的贡献也不同”。

进一步的,如果我们假设所有的反射物均为Diffuse(接受到直接光照的物体、不要求接受物体的材质及其性能; 从这个基础上拓展,我们可以认为一级的反射点对应的BRDF为常数albedo/pai(albedo为反照率),那么我们的出射光=入射光 * 对应的出射比例/对应的面积;我们会将上述积分中的dA部分化简,使得对应的shadowmap,我们只需知道reflect flux和就可以通过存储Incident flux * BRDF就可以预计算保存一阶反射 (在此处补充一下reflect flux是什么,可以理解为次级光源对半球空间发射的总光通量)

Recap:辐射度量学:如果我们想描述单位立体角的强度,为Intensity;单位面积下为Irradance;而描述单位立体角下 单位面积下为ridance。

不过,显然我们无法将每个需要的ShadingPoint都对所有的次级光源分别存储一张ShadowMap计算一次贡献;这个visbility的计算我们直接丢掉了。(囧,次级光源的遮挡关系确实没啥好办法能计算到贡献。) 对于RSM的算法理解+对应的论文相关资料可以参考下述文件: (12 封私信 / 6 条消息) 【论文复现】Reflective Shadow Maps - 知乎

Speed UP

  • 次级光源的可见性没有被考虑,因为难以计算
  • 方向性上可以了解到 并非所有的次级光源RSM都会对光照有commitment。
  • 由于次级光源携带的能量和光照都及其有限,因此我们可以认为对于每个着色点shading point,我们其实只需要考虑相对应近距离的一部分的次级光照就可以了。 由于我们的Shadowmap很难转化到世界坐标,所以此处的解决方案就是将point p投影到Shadow Maps上,只对投影上去的近邻位置进行采样得到好的效果!

What’s in RSM

对于RSM中的深度图,Depth+世界坐标(Shading)+normal+flux(辐通量)+cos角度 这种技术常用于手电筒的光照计算

Pros and Cons

  • pros:shadow map的2pass流程,易于实现
  • cons:
  1. 复杂度取决于光源数+二级反射来说,没有可见度判断,没有对间接光照的可见性测试。
  2. 过多的假设:光滑的反射物体+向ShadowMap投影加入光照
  3. SampleRate和质量的TradeOff(采样光线越多越真却越慢)

这个算法仍然是一个图像空间的算法,而我们再着色的时候,本质上还是进行的投影。不过不会因为图像中没有储存而存在一些无法计算的情况;

Light Propagation Volumes-3D Space

主要用于解决再全局/间接光照时 如果我们能得知针对被着色点,所有不同方向的Radiance值综合是多少我们就可以做此处的光照function了。 是一种优秀的解决办法,可以解决动态问题,且这个过程是实时的。

Key Idea

  • Radiance在传输的过程中是不会发生改变的,(irridance会有随之变化的情况;)因此我们使用体素化的路径用于传播Ridance即可。

Key Solution

我们使用3D的体素进行网格划分,由于ridance是直线传播不变,因此我们直接利用体素方式进行层级分割。

Rendering Steps

Step1:building

  • 确定接收到直接光照的点(确定次级光源的位置) 找到所有直接找亮点表面(可以有选择性的选择对应的光源数量,这一步的算法逻辑类似Shdaow map)
  • 将对应的次级光源点/面注入场景中网格的某些点(体素化分布进去) 我们已经有了对应的次级光源位置,我们将其差分到离散的3D网格体中(在工业算法中,常用三维的网格体解决这个部分的function,每个格子内部都可以作为虚拟的光源,将其向不同方向radiance计算出来,利用这个分布,利用SH进行插值存储(工业界的算法上一般直接使用2阶段的SH进行解决))
  • 沿着体素网格将光源按顺序照亮 根据记录的值向周边的格子进行传递;当其穿过一个格子的六个面时,传递给对应的六个表面的radiance。——>将对应的值存储起来后用SH表示周围的其他格子里面的光照值
  • 重复体素化的过程直到全局的光照都趋于稳定。 Step2:Shading 到这里,我们只需要找到Shading Point所在的体素格子进行插值计算光照就可以得到很好的效果。

Weakness

在进行体素传播时,我们没有考虑遮挡问题,划分体素的时候,做注入时没有考虑遮挡,会产生同个格子内部照亮自己的内部——会存在漏光问题。

  • 这种leaking问题,显然的解决思路1就是传播时细分进行传播/直接利用细网格,不过会有效率问题和速度问题。
  • basline中,在传播的时候我们难以计算遮挡关系,因此我们现在都是假设可以传播。 工业界常用的method就是自适应的划分方式:cascade(一种LOD的层次划分/级联密度)
  • 是一个4 Pass算法

VXGI(Voxel Global illumination)

  • 仍然是一个两趟的算法
  • 相对于RSM,这种算法中次级光源是完全离散为体素格子进行的照明;我们将场景离散成大小不相同的格子,建立出层次体积的格子;我们从把像素层的逻辑转变为体素
  • 这种光线的传播与注入只需进行一次传播! 在Pass1的时候,我们根据记录的次级光源进行传输而在Pass2我们从相机角度出发,我们会记录屏幕空间的每个Shading Point,反演出对应的每个点进行一次(Cone:Glossy的时候利用圆锥体进行trace)Tracing来获取到屏幕空间的光照。

Steps

  • 寻找Patch的直接光照
  • 我们在voxel中记录间接的光照+以及用于反射的表面的法线分布来综合处理对应的光照。我们对每个层级进行插值统一,利用树形状结构逐级细分。
  • 对于glossy表面,我们用ShadingPoint连成锥体,我们来判断体素是都与锥体相交,并根据内部的法线等信息部署进去观察其是否真正相交;好处是这样的锥体截面大小也可以用于动态选取我们选用不同层级的voxel进行体素的信息的贡献。
  • 而对于Diffuse的case,由于diffuse的发布很随机,如果是这种情况下,需要使用若干圆锥进行模拟加和来模拟对应的performance,对于Diffuse的光照逻辑来讲,对应的一些bias和leak影响不大,不必过于严肃考虑。

Cons

体素化需要比较复杂的预处理,对于动态系统的支持效果需要详细调优。

看了一个OpenGL的实现表现,感兴趣可以看看:[2104.00618] Real-Time Global Illumination Using OpenGL And Voxel Cone Tracing

Real-Time Global Illumination(实时全局光照)- Screen Space

屏幕空间上的算法,指的是我们预先了解到 的信息,只有在使用这个效果之前我们能够看到的屏幕空间上的相应信息,也可以理解为在图像空间进行后处理

SSAO(Screen Spcae Ambient Occusion)-屏幕空间环境光遮蔽

笔者在Games104里面有了解过这个问题,这里简要概述。

  • 易于实现,且能够能更清晰的让整体空间提升相应的立体感。
  • 是一种对全局光照的近似算法,且是在屏幕空间进行处理的算法;

Key Idea

  • 在进行计算的时我们不知道间接光的情况,也不知道对于着色点的光线来源,因此我们可以通过假设间接光照是一个常数且来自任何方向光照相同,但是进一步,我们考虑对于shading点的可见性纳入考虑,将阴影/亮度的表现通过乘可见度系数来明确出遮挡影响出的相对关系。(虽说如此,在Shading的时候仍然将材质考虑为Diffuse)

在离线渲染中我们考虑这一项目就可以优化对应的Processing;

Theory

AO的处理方式实质上就是对RT的一种近似; 利用对应的公式,我们把可见性的部分拆分,v项独立,剩余部分使用间接光照: 从这个逻辑来讲,我们的平均可见性(0-1)就是可见项 * 间接光项。(AO是我们假设All Diffuse且光照相同,因此Li Fr均为常数。效果为albedo/pai) 经过刚才的传播逻辑,我们将这个公式拆分优化后就可以得到:

Deeper Enderstanding

  • 这种归一化的近似逻辑,本质上是要求在G(x)的覆盖积分的范围内,求出F(x)在积分逻辑(广义积分中值定理)下的求平均,获取开积分后的稳定解。
  • Shadow Map时此处是对立体角积分,近似没问题,在此处我们假设的diffuse和brdf都是常数,在这个逻辑下,此处的积分是准确的而非是近似
  • 我们在间接光的拆分后,为什么不是上下都切换积分从dx变为Zeita项?在上述SSAO的第一页PPT中存在这个悬而未决的部分。 对于立体角来说,我们从单位半球切换为单位面积积分,这样的话我们的总体的域的unit如下,我们可以近似的将其直接理解为是一种积分换元:

Industry Method

  • 我们需要寻找限制的半球范围内是不是有遮挡效果,否则所有的光照最终都会被遮挡,而过小的判断半径会影响SSAO能够接收到的光照,会相对变暗。
  • 我们这并不是真正的使用ShadingPoint进行RayTracing,而是利用以这一点对应的深度图创建一个球体,在球体内随机采样撒点判断点在物体的内部还是外部,利用这一点的值和表面的深度图进行比较,判断这一点是否正确可见。 (注意,这个办法我们需要Depth Texture)

见图2中的点,出现内凹的部分,我们没有办法处理这种被双向遮挡的部分的结果。 见Games104专栏我的AO笔记,现在的Normal逻辑下我们可以通过ShadingPoint处的发现信息,只对法线朝向为轴向的半球做Sampling,会极大优化可见性相关的问题。

我们在Sample时,比较常见的AO Map处理方式是利用低采样16-64spps的方法后进行后处理滤波降噪,乘算在间接光内的时候,这一效果的噪声效果也不会很明显,ao的效果比较显著。

Weakness

比较老生常谈的False Occusion:很近处的物体遮挡了由于摄像机角度问题导致的远景处存在着很深的阴影。

Future

SSAO-HBAO-GTAO-SDAO-RTAO…这一部分的展开在Games104有更详细的介绍

SSDO(Screen Space Directional Occusion)

  • 是一种针对AO的改进,获得更优化的ao表现,其效果更接近ray tracing。 DO的逻辑更接近Path Tracing,从出发点开始迭代。模拟光线从附近表面反弹后的颜色溢出(Color Bleeding),不仅变暗,还能看到颜色。
  • 从这种逻辑上讲:AO的间接光照来源于远处(环境光),DO的间接光照来源于近处的反射;我们更关注间接反射部分光照的亮度,将其被遮挡部分面对应ShadingPoint的贡献积分加和

SSDO效果常被集成优化。虽然直接提及“SSDO”的情况变少,但它的核心思想——在屏幕空间内模拟间接光(颜色溢出)——已经被集成到更高级的全局光照解决方案中。许多游戏和引擎的综合光照系统,就包含了类似SSDO的优化变体。

Weakness

Screen Space会丢失一些信息,比如我们无法知道屏幕空间背面处能得到的更多信息,如下图垂直侧面时的bleeding效果差强人意。 这种方法下大部分情况下对于全局的光照进行处理只能在局部有好的表现效果。

SSR(Screen Space Reflection)-屏幕空间反射

Intro

  • 不需要三维原始数据。只保存相机对应层的数据
  • 表现形式为光线追踪 目标:
  1. 对可见场景和光线进行求交逻辑
  2. 计算任何光线和场景的这种Shading的贡献并加和。 例子:光滑地面的SSR(反射本身也是全局光照的一种体现。)

Basic Idea

  • 对应任何一个像素位置,我们都能了解ShadingPoint对给定光线的镜面反射的射线方程,我们和Screen Space的物体进行一次求交,我们把对应的Caputre信息返回给像素作为反射的commit。——而对于这种情况下,我们多取一些方向,对其进行法线的扰动,取多跟光线根据余弦量加权计算贡献 就可以实现从镜面->粗糙的反射。

Detail Function

针对场景来说光线出发后反射,反射时,我们以初始点开始每次初步迭代直到光线来到物体表面深度层的背后,这样在两个steps之间就可以得到对应的光照值。

步长的选取会显式的决定leaking和复杂度的问题,也是需要解决的核心问题之一

Hierarchical Ray Tracing
  • 在很多情况下,我们无需进行过细的层级进行迭代。通过这个逻辑,我们可以考虑进行逐步的迭代,以多层级的Depth Texture,类似LOD的思想预存储好不同精细度的部分。
  • 这种情况下的LOD插值并非是取平均进行mipmap操作,而是把不同Steps上的距离最近 值用于记录,这种情况下,因为我们每一层都取最小深度进行近似,这样的话类似预计算的树形结构,如果上级不相交,那么更深的层级更不可能相交。 简单的伪代码算法流程如下,会根据Steps的是否Hit进行逐渐变大/变小的Steps(一直没有相交逐步拓宽步数,有相交超过后减小步数。)注意,我们需要判断相等条件时是否视为相交。

HiddenProblem

  • 从几何角度来说,在反射的时候,由于屏幕遮挡,我们无法在反射的时候无法记录到屏幕空间没有记录到的颜色部分,而没有反射,只记录了最近处的深度。这一问题是屏幕孔吉纳算法带来的problem;
  • 不是所有被反射的物体都会在屏幕内被记录,这种情况会影响到反射的部分只能计算到镜头内的可反射部分,多余的部分会被Cut,解决这个方案大多使用菲涅尔效应或者在原理的部分做模糊处理(因为菲涅尔夹角问题可能你真的看不到2333)

Shading

  • 着色逻辑和Path Tracing是完全一致的。我们仍然需要假设反射物需要是假设为diffuse的,否则对应的反射Function无法被找到,你不能完全查找随机方向上的光线来测试反射。
  • 这种方法利用RayTracing逻辑,可以完美的处理遮挡的关系,对可见性有良好且合理的可见性检查。 这种算法是逐像素计算的,因此我们只要知道对应位置的粗糙度和法线位置就可以获得良好的采样效果

未来的加速核心思路是利用重要性采样来优化加速一下,来保证采样方向和聚集方向基本保持一致。蒙特卡罗方法在PDF的核心方向也会更加的集中。 我们也可以通过可能一个ShadingPoint可以和其他的部分进行处理,将对应位置相同的反射点进行Reuse再进行优化。通过预先模糊对应的图,这样我们也可以利用Gaussion模糊的Map进行采样来获取diffuse的反射部分(不过这种split sum的方式来做很多时候无法避免split sum带来的深度相关影响,有差别于世界空间的可以进行清晰拆分,这里需要进行一下屏幕空间的双边滤波再做更详细的处理。)

Pros And Cons