基于 Surfel 的实时全局光照方案(Surfel-based Global Illumination)
阅读原文时间:2023年07月15日阅读:1

目录

surfel,可以理解成贴在世界空间中某个 mesh 上的圆片(有点像贴花),在几何上定义为:顶点 + 法线 + 半径

在不同的实现方案中,surfel 承担的角色可能会不一样;对于一般的 Surfel GI 方案而言,往往是基于 radiosity 方法:往场景里的 mesh 疯狂贴圆片(surfel),这些 surfels 用来探测光照并缓存起来,和 probe 非常相似。但是由于其是贴在物体表面上,而不是像 probe 放置在空间中,因此光照效果要更高质量些。

因为渲染的像素往往就是物体表面的像素,而这些像素更加接近于贴在表面的 surfel 而非接近于置于空气中的 probe。

Global Illumination based on Surfels [SIGGRAPH 2021]

在 SIGGRAPH 2021 的 GIBS(Global Illumination based on Surfels)实现中,就是使用 surfels 来探测并缓存光照来实现 indirect diffuse lighting(注:不包含 direct lighting 也不包含 specular)

传统 path tracing 就是让每个 pixel 去做 ray tracing,而用 surfel 做 ray tracing 的开销会大大减少:因为 surfel 比 pixel 的数量远远要少;并且通过控制 surfels 的数量,还可以弹性控制性能和效果的平衡。

本文将主要基于 SIGGRAPH 2021 GIBS 方案来讲解 Surfel,中间也结合一些 wicked engine 的实现方式,以供扩宽思路。

Surfel 持久化存储

我们希望 surfel 是持久化存储的,也就是说即使 surfel 不在屏幕范围内,其生命周期仍可以不结束,这样就不会白白丢掉 surfels 累积的计算。

也就是说相对于 SSGI 方法,Surfel GI 还可以保留有屏幕外的信息。

  • transform ID(贴在哪个mesh上):可由 G-buffer(Visibility Buffer)获取 ID
  • local position(相对于 mesh 的位置):可由 G-buffer 获取 depth,推出 world position 再乘 transform 的逆变换得到
  • local normal(相对于 mesh 的朝向):可由 G-buffer 获取 world normal 再乘 transform 的逆变换得到
  • irradiance(存储累积探测后的 irradiance):通过 tracing 探测光照并累积起来得到的 irradiance
  • depth buffer(存储半球方向上的最小深度):用于判断 visibility

场景中的 mesh 可能会发生各种诸如移动变化,而 surfel 通过记录 transform id 和 local position(而非世界坐标),从而可以附着于动态的 mesh 表面上。

在具体实践中,surfel 数据其实内部属性还会进一步分离:

  • 几何结构(如:normal, position, transform id)
  • accumulation data(如:short mean,mean)

首先,有些属性经常需要被查询(热数据),而另一些属性则相对没那么频繁被用到(冷数据),这会对 cache 更加友好;其次,有些属性块需要进行 double buffering 来减少共享冲突,增加效率,而不是对所有属性都进行 double buffering。

但 surfels 不可能无限增多(存储空间有限),还需要有一定的回收机制,判断一个 surfel 是否应当回收取决于因素(启发式 heuristic):

  • 现存 surfels 的数量
  • 最后一次出现在屏幕的时间间距
  • 与 camera 的距离
  • tile 的覆盖数量
  • 附着的 mesh 是否被移除了
  • ….

数据结构实现:

  • Alive Buffer 专门存储 surfel data,这样就可以通过 index 访问到对应的 surfel 数据
  • Alive List 存储 surfel index 来表示有哪些 surfels 存活
  • Dead Buffer 存储 surfel index 来表示有哪些 surfels 被回收了,其本质是一个栈

Surfelization

接下来就是如何生成 surfel 的问题。

SIGGRAPH 2021 GIBS 采用了基于屏幕的 surfel 放置方案:将屏幕划分为 16*16 个 tiles,每个 tile 覆盖的 surfels 数量如果太少了,则在该 tile 中最少 surfel 的屏幕像素点来生成新的 surfel。

为了生成 surfel,我们需要根据访问屏幕像素对应的 G-Buffer 属性(transform ID,world normal,depth),并以此来初始化 surfel 的几何结构:

此外,对于 Skinned Mesh 情况则需要额外处理,因为它和一般的 rigid geometry 不同,它的表面是会形变的,不方便贴 surfel 上去。

解决方式:贴在权重最大的骨骼上而不是 mesh 上,因此针对 Skinned Mesh 情况,要使用 bone id 而不是 transform id;尽管这种解决方式不准确,但是实际表现出的效果是可接受的。

此外,我们还希望生成的 surfel 符合 LOD 思想(即远处的 surfel 没必要那么高质量那么精确),规定其在屏幕空间的投影大小需要大致相同,也就是说场景远处的 surfel 半径会很大,而近处的 surfel 半径会很小。

Acceleration Structure

目的:希望通过一个空间加速结构,输入一个世界坐标可以快速查询到邻近的 surfels

难点:如何合理安排一个存储结构,既能保持高效查询,又能不耗费太多存储

  • 限定以 camera 为中心的一定范围内,每帧都使用新构造的 grids
  • 每个 grid 存储一个 surfel id 列表,但因此在 surfels 比较密集的地方, grid 可能会漏掉一些 surfels

列表可采用 index + offset 表示,而非使用固定长度的列表,这样可以进一步节省存储空间

理想情况下,1 个 surfel 的半径大小不应该超过 1 个 grid 的大小,否则可能会覆盖到过多的 grid,因此为了避免场景远处生成的 surfel 半径过大覆盖了太多 grids,SIGGRAPH 2021 GIBS 采用了 grid 的变种方案:

  • 近处的 grids 均匀分布,远处则采用梯形 grids (类似视锥体指数分割那样,越远的 cell 越大块)

Final Gathering

一般来说在每帧下,每个 surfel 需要生成相同次数的 ray,但是我们可以对比较重要的 surfel 多生成些 rays。

Modified Exponential Moving Average Estimator, MSME [2019] :如果 surfel 的 irradiance 变化很小,那么说明基本收敛,则少生成些 ray;如果 irradiance 变化很大,那么应该在本次加多些 ray 的次数。这样在同等性能下,可以实现更快适应场景变化。

GIBS 采用了 MSME 的实现方式,引入长期平均值(mean)和短期平均值(short mean),用这两者的相差体现 irradiance 的变化程度。

实际上在当前帧,一个 surfel 要发出 ray 的数量应综合取决于以下因素:

  • local variance(irradiance 变化程度)
  • 最后一次出现在屏幕的总计帧数
  • surfel 生成后的总计帧数
  • 全局所生成的 rays 数量

重要性采样,即让 ray 更大概率地指向 pdf 比较大的方向,除于 pdf(归一化)后得到更快收敛的积分值。pdf 越接近原分布函数,则越快收敛。

BRDF pdf:使用 cosine PDF 来做 diffuse 的 importance sampling

Lighting pdf:利用球面方向映射八面体的方式,来记录各立体角方向的历史累积 irradiance,做成一张 radial irradiance map

  • 一个 texel 代表一个方向,texel 的值则存储了代表该方向的 irradiance
  • irradiance 越大说明该方向更容易 trace 到有效光

根据 pdf 选取 texel 时,可使用层次化积分优化(即将 irradiance map 做成 mipmap,先遍历大步再遍历越来越小的步),原本遍历所有 texels 的复杂度 \(O(n)\) 便可以降低为 \(O(logn)\):

每个 surfel 对应生成了若干个 rays(根据 importance sampling 来生成方向和 pdf)后,我们就需要开始收集光照并计算出 surfel 的 irradiance。

注:因为我们要做的是 indirect lighting 效果,因此 surfel 的 irradiance 只包含间接光照,而并不包含直接光照。

对每个生成的 ray,从 surfel 出发,射出后得到第一个 hit point:

  • 一次间接光照:从 hit point 出发,再往光源发射 shadow ray(若到达光源前命中物体,则说明可见性 \(V\) 为 0,即该光源没有贡献光照),计算出 hit point 的 direct lighting

    \[E_{dir}(x) = \sum_{j = 0}^{lightNum}{E_{light[j]}(x)* V(x \to {light[j]})}
    \]

wicked engine 在实现一次间接光照的时候,并没有对所有光源计算光照贡献之和;而是采用了多光源俄罗斯轮盘赌的方式,随机从光源列表抽一个光源出来计算贡献,并将这个光源的光照贡献乘于光源数量来作为结果。

  • 二至无限次间接光照:通过 grid 获取 hit point 附近 surfels,并将它们的 irradiance 混合(根据距离权重)得到 hit point 的 irradiance \(E_{indir}(x)\)

\[E_{indir}(x) = \frac{\sum_{k=0}^{nearSurfelNum} \mathrm{DistWeight}(x_{nearSurfels[k]},x) * E_{nearSurfels[k]}}{ \sum_{k=0}^{nearSurfelNum}\mathrm{DistWeight}(x_{nearSurfels[k]},x) }
\]

接着就能计算出本次 ray 的 radiance:

\[L(i) = \frac{(E_{dir}(x_i)+E_{indir}(x_i)) * \mathrm{albedo}(x_i)}{\pi}
\]

其中,\(i\) 代表了第几个 ray,\(x_i\) 代表了第 \(i\) 个 ray 的 hit point 位置。

当所有的 ray 都计算出 radiance 后,就可以计算出 surfel 在本帧的 irradiance:

\[E_{surfel} = \sum_{i=0}^{rayCount} \frac{L(i) \cos(\theta)/pdf(i)}{rayCount}
\]

比较粗暴的方法就是使用固定的历史 irradiance 混合权重(一般为 0.8~0.9),但可以考虑根据 estimator 的评估来作为 temporal 的混合权重参考:

  • local variance(irradiance 变化程度)

例如 irradiance 变化程度很大时,我们认为历史帧参考意义不大,因为历史混合权重应当小些;相反,就证明基本收敛,就调大些历史混合权重。

当 surfel irradiance 变化过大时(远远没有收敛,例如发生在刚生成新的 surfel 时),我们可以多多参考附近的 surfel irradiance,相当于做了一次空间滤波。

实际操作就是利用空间加速结构来快速查询该 surfel 周围的其它 surfels,将它们的 irradiance 按以下因素来加权混合:

  • distance
  • normal
  • depth function

GIBS 方案总结

生成 surfels:基于屏幕空间

  • 生成 surfel:屏幕空间分成 16×16 个 tiles,每个 tile 检查内部 surfels 的数量,若太少则根据 G-Buffer 属性来生成新的 surfel
  • 更新 acceleration structure:每当有一个 surfel 生成时,找到其所覆盖的 cells,对这些 cells 的 surfel 列表都添加该 surfel

更新 surfels:对 surfels 容器进行遍历

  • 根据 transform id ,local position, local normal 更新 world position,world normal
  • 回收 surfel:通过启发式判断当前 surfel 是否需要移除,若需要则进行回收
  • ray generation:根据 surfel 的 irradiance 收敛程度等属性来确定要生成的 rays 数量(假设为 n 个),提前占好 ray buffer 的 n 个位置(通过 ray offset + ray count 表示)

ray tracing:对 ray buffer 遍历

  • ray guiding:根据 surfel id 获取 pos, normal,并根据 importance sampling 生成该 ray 的方向和 pdf
  • ray tracing:通过 ray tracing 收集一次间接光照和二至无限次间接光照,得出 radiance 结果除于 pdf 后即为计算出的 irradiance

integrate:对 surfels 容器遍历

  • 访问 surfel 在 ray buffer 对应的 n 个 rays,将它们的 irradiance 结果平均一下即为本帧 surfel 的 irradiance
  • estimator:根据长期平均和短期平均的相差值来评估 irradiance 的收敛程度
  • temporal filtering:根据收敛程度来决定 temporal 混合权重
  • irradiance sharing:根据收敛程度来决定是否启用;若启用则查询周围的 surfels,混合它们的 irradiance

pixel 着色:基于屏幕空间

  • 重建 pixel 的世界坐标位置,找到其所在 cell ,获取该 cell 的 surfel 列表(意味着这些 surfel 都离该 pixel 很近)
  • 遍历这些 surfels 并根据它们的 normal 和 position 来衡量权值,最后加权和混合起来得到该 pixel 的 irradiance
  • 有了 irradiance 就可以做 shading 计算了

Surfel GI 的优势:

  • 比起 DDGI 有更高质量的 irradiance 混合方式
  • 比起 SSGI 可以有屏幕外的信息(虽然放置还是基于屏幕)
  • 整个流程完全动态,都是 runtime 计算的
  • 持久化存储,避免浪费掉累计的 irradiance 计算

Surfel GI 的缺点:

  • 有一定 Light Leaking 问题:即使有 Radial Gaussian Depth 方法,由于分辨率过低还是很容易出问题,性能与 artifacts 的取舍,但配合 AO 可能会有不错的效果
  • 无法实现半透明物体的 GI 效果:surfel 的生成依赖于 G-Buffer,因此贴不上半透明物体,不过可以将 Surfel GI 的思路套用在 probe-based GI 的方案上
  • 只对 diffuse 效果有比较好支持(毕竟 surfel 主要提供的是 irradiance),可能不那么适用于 specular 效果

改进思路

参考

ps:PathTracing和自己的实现对比图(仅对比间接光照效果,无直接光)

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章