基于 Surfel 的实时全局光照方案(Surfel-based Global Illumination)
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 还可以保留有屏幕外的信息。
surfel 数据组成
- 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。
surfel 回收机制
但 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 的问题。
screen-based placement
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
难点:如何合理安排一个存储结构,既能保持高效查询,又能不耗费太多存储
grid
- 限定以 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
ray generation : MSME
一般来说在每帧下,每个 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 guiding : importance sampling
重要性采样,即让 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)\):

ray tracing : indirect lighting
每个 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)\)
接着就能计算出本次 ray 的 radiance:
其中,\(i\) 代表了第几个 ray,\(x_i\) 代表了第 \(i\) 个 ray 的 hit point 位置。
当所有的 ray 都计算出 radiance 后,就可以计算出 surfel 在本帧的 irradiance:
temporal filtering
比较粗暴的方法就是使用固定的历史 irradiance 混合权重(一般为 0.8~0.9),但可以考虑根据 estimator 的评估来作为 temporal 的混合权重参考:
- local variance(irradiance 变化程度)
例如 irradiance 变化程度很大时,我们认为历史帧参考意义不大,因为历史混合权重应当小些;相反,就证明基本收敛,就调大些历史混合权重。
irradiance sharing
当 surfel irradiance 变化过大时(远远没有收敛,例如发生在刚生成新的 surfel 时),我们可以多多参考附近的 surfel irradiance,相当于做了一次空间滤波。
实际操作就是利用空间加速结构来快速查询该 surfel 周围的其它 surfels,将它们的 irradiance 按以下因素来加权混合:
- distance
- normal
- depth function
GIBS 方案总结
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 计算了
GIBS 方案优缺点
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 效果
改进思路
参考
- [1] SIGGRAPH 2021 | Global Illumination based on Surfel
- [2] Github | Wicked Engine | Janos Turanszki
- [3] I3D 2016 | Precomputed Illuminance Composition for Real-Time Global Illumination
- [4] Ray Tracing Gems | 2019
ps:PathTracing和自己的实现对比图(仅对比间接光照效果,无直接光)
作者:KillerAery
出处:http://www.cnblogs.com/KillerAery/
本文版权归作者和博客园共有,未经作者同意不可擅自转载,否则保留追究法律责任的权利。