基于 Probe 的实时全局光照方案(Probe-based Global Illumination)

探针(Probe) 或者说 光照探针(Light Probe),简单理解就是场景中的一个点然后给予这个点去往四面八方收集光照或者说探测光照的能力,并把往四面八方的入射光 radiance 记录下来(其实就是记录周围的光照,即光场)。在我们对某个 shading point 渲染的时候,就可以利用它附近 probes 的光场信息粗略估计出它所受到的光照。

这里以前我写的是环境光照信息,实际上是概念混淆了,周围的光照 ≠ 环境光照。

Precomputed Probe

在场景里预先放置若干个 probe,为每个 probe 预计算出局部(因为只能处理静态物体和静态光照,无法囊括动态物体和动态光照,所以是局部的)光场信息并存储后,在运行时就可以通过物体周围 probes 的光照信息来插值来得到 shading point 此时受到的局部光照。

预放置 probes

基于预计算的 Probe 方案往往意味着需要进行预放置,主流有三类放置方式:Tetrahedral Tessellations,ILC,VLM

四面体镶嵌(Tetrahedral Tessellations)

在场景中预先放置自定义位置的 probe ,这些 probe 将自动相连成一个个四面体。而为了增加这种 GI 方案的真实感和避免过多的 probe 带来的存储开销,一般应当把 probe 在光照发生明显变化的地方(如明暗交接处)放密集些,而在光照不怎么变化的地方可以稀疏地放置。

ps:将这些 probe 进行三角化(将空间切分成四面体)需要用到 Delaunay Triangulation 完美三角剖分算法。

  • 手动放置的方式比较费人力
  • 运行时,查找 shading point 所处四面体的运算量较高:四面体的分布并不是均匀的,最糟糕没有优化的情况下需要暴力遍历每一个四面体判断 shading point 是否在该四面体里面,无论对 CPU 还是 GPU 运算都不太理想

基于 probe 的方案是需要经常用到这种查询操作:给定一个世界坐标,查询出它周围邻近的 probes

Indirect Light Cache

Indirect Light Cache, 即 ILC(间接光缓存),是 UE4 的方案,它基本上是在静态物体表面法线向上自动地均匀放置 probe ,这是基于假设受光照影响最大的地方都是在靠近静态物体的地方。

实际上,ILC 还包含两种 probe 放置方式:通过手动放置 Lightmass Importance Volume(重要光照范围)来限制烘焙光照采样范围,或 Lightmass Character Indirect Detail Volume,来增加一段均匀放置 probe 的区域。

  • ILC 通过 CPU 寻找物体周围的 probe:所有的 probe 的数据将保存到八叉树中以方便物体找到周围的 probe

Volumetric Light Map

Volumetric Light Map, 即 VLM(体积光照贴图),也是 UE4 的方案,它使用网格(Grid)采样点保存 probe 数据,而在静态物体表面附近,会对网格进一步细分。

  • VLM 通过 GPU 来寻找物体周围的 probe :所有 probe 的数据将烘焙至贴图中,不同细分层度的网格将使用 level 不同的贴图,这样可以方便地在 GPU 中进行逐像素的三线性插值

  • VLM 比 ILC 的 probe 要多更多,从而 VLM 的 GI 效果更佳,但要存储的数据更多

  • VLM 的存储结构决定了可以通过 GPU 算法来寻找 probe,这比传统的 CPU 算法性能更加可观(即便 VLM probe 数量要多得多)

  • VLM 更适合将 probe 应用到体积雾效果中,这是因为 ILC 往往不在几乎没什么物体的空间放置 probe

UE 4.18 以后使用 VLM 代替了 ILC 方法作为 UE4 默认的 probe 方案。

ILC vs. VLM 效果图:

烘焙光照信息

基于预计算的 Probe 方案在收集光照信息的时候,也采取预计算(烘焙)的方式。这意味着只能对静态光照(静态光源、静态物体组建成的静态场景)进行收集。

SH lighting

在离线阶段,每个 probe 会通过比较高质量但耗时的渲染方式(一般可以使用 path tracing 等方式)来收集静态光照,说白话也就是在 probe 上往四面八方去探测光照。同时,探测到的静态光照信息信息将以 SH 的方式(而不是 irradiance map)的形式记录到该 probe 中。

由于场景中有很多个 probe,要是每个 probe 都存储一张 irradiance map 的存储和带宽开销会非常大,因此往往采用 SH lighting 方式(每个 probe 仅需要存储十几个或者几个 SH 系数)会更加可行。

重建 shading point 光照信息

有了预先放置好的 probes ,又有它们各自预先烘焙的光照信息(SH 系数),那么我们就可以在运行时对 shading point 着色:

  1. 根据 shading point 的世界坐标找到其邻近的 probes
  2. 对这些 probes 的 SH 系数以距离为权重进行混合,得到 shading point 的 SH 系数(比较粗糙的光照信息)
  3. 根据 SH 系数就可以重建 shading point 的光照信息信息

优缺点

优点:

  • 预计算能够节省大量的计算来实现还可以的 GI 效果

缺点:

  • 由于是预计算,因此不支持动态光照
  • 对于大场景需要很多的存储空间,并需要设计一定复杂程度的空间结构来管理查询

DDGI(or RTXGI)

Dynamic Diffuse Global Illumination(DDGI),则提供了一种支持动态光照的 probe 方案。

NVIDIA 的 DDGI 实现版本则一般称之为 RTXGI 技术,因此基本上说起 RTXGI 就是相当于在说 DDGI

动态/预放置 probes

DDGI volume

DDGI volume:一个立方体积区域,里面均匀放置着 probes(类似于 grid),在 DDGI Volume 内的物体就能被间接光照亮,实现 GI 效果。虽然 volume 也需要手动放置,但是粒度更大,比起手动放置 probe 工作量要少得多。

不过,与前面预放置的固定 probes 不同:

  • volume 既可以预先固定在某个位置(静态放置),也可以动态改变位置(动态放置)
  • 动态放置 volume:需要实时探测几何信息 + 光照信息
  • 静态放置 volume:需要实时探测光照信息,而对于几何信息则可以选择预计算或者实时探测

在实践中:

  • 对于静态放置:会先放置一个大号的静态 volume 囊括整个场景(常用于表现室外间接光),然后再在场景中各个房间都放置一个静态 volume(更加高清晰度表现室内间接光)

  • 对于动态放置:往往会多个大小不一的 volumes 绑定到 camera 上,这样就可以跟随 camera 移动对视野内的一定范围的物体应用间接光照,并且具有质量上的 LOD 效果(近处采用高精度 volume,远处采用低精度 volume)

  • 在嵌套多层的 volume 之间会做一个线性淡出从而避免明显的边界

See the source image

探测动态光照信息

DDGI probe 采取 ray tracing 的方式去实时探测动态光照信息,并且使用了 八面体映射(octahedral mapping) 技术来缓存收集到的光照信息:将球面方向映射成一张 2D texture,一个 texel 对应一个立体角方向,texel 的值即代表了其对应立体角方向的值。

preview

RTXGI 实现中,ray tracing 就需要使用到硬件光追 API(需要 RTX 系列显卡)

DDGI probe 的数据组成:

  • 6×6 irradiance texture:存储每个 texel 方向的 irradiance \(E(\omega)\)
  • 14×14 moment texture:存储每个 texel 方向的深度 \(r(w)\) 及深度的平方 \(r^2(w)\),以便进行 variance depth test

variance depth test 在下文 light leaking 问题中再详细介绍。

计算 radiance

  1. probe 均匀向球面上射出若干个 rays,并记录每个 hit point 的 position,normal,albedo 并将结果存入分别存入三张 texture

如果采取预计算的 volume ,那么这一步操作就会提前到预计算阶段并保存好,从而运行时让每个 probe 无需射出 ray,直接访问保存好的那三张 texture

接着我们需要计算每个 hit point 的着色结果,这样才能计算出对应 ray 的 radiance

  1. one-bounce lighting:通过 position,normal,albedo, rayDir 进行 hit point 的直接光照着色

\[L_{one} = \sum_{lights} (L_{light} * f * \cos \theta) \]

  1. multi-bounce lighting:通过 position,normal 并利用周围 probes 的上帧 irradiance texture 来重建 hit point 的光照信息,并进行间接光照着色

\[L_{multi} = \frac{E(position, normal)* albedo}{\pi} \]

间接光照着色必须使用 diffuse BRDF,即乘 albedo 除 PI。因为 irradiance 丢失了入射角相关的光照强度信息,无法使用 specular/glossy BRDF。

  1. 将结果写入进 radiance texture

\[L(rayDir) = L_{one} + L_{multi} \]

更新 probe 的 irradiance

  1. 使用 radiance texture 来更新 probe 中的 irradiance texture

\[E(texelDir) = \frac{\sum_{\text {rays}} L(rayDir)\cdot \max (0, \cos \theta )}{\sum_{\text {rays}}\max (0, \cos \theta )} \]

  1. 使用 position texture 来更新 probe 中的 moment texture

\[\begin{aligned} r(texelDir) &=\frac{\sum d(rayDir) \cos ^s \theta_i}{\sum \cos ^s \theta_i} \\ r^2(texelDir) &=\frac{\sum d(rayDir)^2 \cos ^s \theta_i}{\sum \cos ^s \theta_i} \end{aligned} \]

position texture 代表了某个方向的光线交点的世界空间坐标位置,通过变换可以计算成该方向的最近 depth,这将在后面用于 probe 的 depth test,去解决一些漏光问题。

  1. temporal filtering:

\[E_{new}(texelDir)=\operatorname{lerp}(E_{old}(texelDir),E(texelDir), historyWeight ) \]

重建 shading point 光照信息

在最后我们需要对 shading point 进行着色的时候,就可以通过更新好 irradiance 的 probes 来重建 shading point 的光照信息:

image-20221025015858864

  1. 根据 shading point 的世界坐标找到其邻近的 8 个 probes

  2. 对每个相邻的 probe:

    • 计算出 shading point 到 probe 的 \(dir\)

    • 测试 \(dir\) 是否与 \(n\) 面向同一侧:若 \(dot(dir, n)\le 0\),则跳过该 probe

    • 根据 \(dir\) 索引到 texture 中对应的 texel ,得到 \(E(dir)\) , \(r(dir)\)\(r^2(dir)\)

    • 计算 shadow 系数:根据 \(r(dir)\)\(r^2(dir)\) 来对 shading point 进行类似 shadow map 的测试

    • 该 shading point 受到的光照信息 irradiance 即为 \(E(dir)*shadow\)

  3. 对这些通过测试的 probes 算得的 irradiance,以距离为权重进行混合便能得到 shading point 的光照信息

重建光照信息实际上在 DDGI 的流程走了两次,一次在计算 ray radiance 那块需要计算 hit points 的着色,另一次便是在这里需要计算 shading points 的着色。

优缺点

优点:

  • 支持动态光照信息
  • 在预计算模式下,还能避免运行时 ray tracing 的开销
  • 若将 volume 绑定在 camera 上,则存储空间的开销增长与场景大小无关

缺点:

  • 硬件 ray tracing 的开销可能仍然很大
  • 在预计算模式下,对于大量动态物体的场景可能会很容易穿帮,因为几何信息只包含静态物体
  • 不能放置太多 probe,因为每个 probe 需要更多存储空间了(textures 相比于 SH 系数)
lp_volume_10

有关于优化和改进 DDGI 的方案,可以详见于 动态漫反射全局光照Dynamic Diffuse Global Illumination | 知乎 [宇亓],这方面个人觉得宇亓的文章会更加全面。

Screen Space Probe

动态放置 probes

uniform placement

Uniform:在屏幕上均匀放置 probe;一般使用 2d texture 存放 uniform probes。

adaptive placement

Adaptive:在 uniform probes 之间,每隔一小段距离尝试进行插值测试,对插值失败的地方额外放置 probe;一般使用动态数组存放 adaptive probes。

所谓插值测试,一般是指根据 uniform probes 的世界坐标进行插值得出当前屏幕点的插值世界坐标,如果与当前屏幕点的实际世界坐标相差太大,那就意味着该位置出现几何上的突变,从而导致插值失败。

image-20220822021720949

UE5 Lumen 就是采取了 screen space probe 的方案。

探测动态光照信息

基本流程和 DDGI 差不多,只不过由于 screen space probe 是贴在表面上的,因此它只需要探测表面法线为中心的半球面(而不是探测整个球面)。

并且,它在八面体映射(octahedral mapping) 的基础上再做一步转换就能映射到仅含四个面的 texture 上(即存储空间可以节省一半)。

image-20220823013453586

spatial filtering

由于 screen space probe 是贴在屏幕上的,因此也可以充分利用屏幕信息(一般是G-Buffer)来作为 probe 层级 spatial filtering 的指导。

优缺点

相比于空间中均匀放置 probe 的方案,screen space probe 具有

优点:

  • 对性能友好:基于屏幕的方案所需要的 probes 数量明显要少很多
  • probe 的利用率更高:贴合在物体表面上的 probes 离物体表面的距离更近,因此上 irradiance 的表示更加贴切和准确;而在空间中均匀放置的 probes 可能会有一些压根没怎么用到,利用率更低

缺点:

  • 能量损失更严重:ray 命中的地方很可能不存在 screen space probe,即丢失了屏幕可见几何表面以外的光照信息。因此,一般来说 screen space probe 会搭配别的 GI 方法补上这块的空缺。
  • 很难支持体积渲染:丢失了空间中非物体表面的 irradiance 信息,几乎不能支持体积渲染

Light Leaking 问题

不过 probe 经常会遇到错误的 Light Leaking (漏光)问题:受到几何上不应该存在光照关系的 probe 影响,常见于墙壁遮挡的内外侧。

例如,下面的一个 probe 生成在室内,我们预期室外的墙体是亮黄色的,室内的墙体是暗灰色的:

image-20220821205719640

结果由于 probe 方法没有考虑遮挡,而是直接且错误地进行了对相邻的 probe 插值,从而室外本该明亮的一侧变暗,室内本该黑暗的一侧发生 light leaking:

image-20220821205930337

标记法

这个方法很直观,就是给 probe 额外一个标记属性:是否在室内(例如1为在室内,0为在室外);这样我们要拿 shading point 周围的 probes 来插值时,根据 shading point 在室内外的情况来选择用哪种标记属性的 probes 来进行计算。

image-20220821211737411

虽然粗暴直接,但是开销低廉,实际效果很 work,很多游戏工业界都会采用的方法(例如原神)。在一些方案中,这些标记也可以由自动化生成,而无需人工生成。

shadow-map-like

一些 probe 方案将半球方向上每个 texel 方向的深度存入到基于八面体映射的 depth texture 上。

那么在重建 shading point 光照信息时,我们就可以使用类似 shadow map 的思想来检测 shading point 到该 probe 的光路会不会被遮挡,从而计算出该 probe 对 shading point 的贡献:

\[d_x = x_{probe} - x \\dir = normalize(x-probe) \]

\(x_{probe}\) 为 probe 的位置,\(x\) 为 shading point 的位置。

\[shadow = d_x > d(dir)?0:1 \]

也可以,可使用类似 VSSM 的思想,基于切克比夫不等式(Chebyshev’s Inequality) 去做光滑的遮挡测试(避免硬边缘) :

\[\mu = r(dir)\\ \sigma = r^2({dir})-r(dir)*r(dir) \]

\[shadow = P(X>d_x) \approx \frac{\sigma^{2}}{\sigma^{2}+(d_x-\mu)^{2}} \]

参考

posted @ 2022-10-26 14:35  KillerAery  阅读(645)  评论(0编辑  收藏  举报