基于 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和自己的实现对比图(仅对比间接光照效果,无直接光)
基于 Surfel 的实时全局光照方案(Surfel-based Global Illumination)的更多相关文章
- 基于预计算的全局光照(Global Illumination Based On Precomputation)
目录 基于图像的光照(Image Based Lighting,IBL) The Split Sum Approximation 过滤环境贴图 预计算BRDF积分 预计算辐射度传输(Precomput ...
- SVO实时全局光照优化(里程碑MK0):Sparse Voxel Octree based Global Illumination (SVO GI)
完全自主实现,bloat-free.再次声明,这不是UE.U3D.CE.KlayGE! 老规矩,先贴图.后面有时间再补充描述. 1. 支持多跳间接全局光照2. 支持vxao/so.vxdiff/spe ...
- 基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)
目录 Reflective Shadow Maps(RSM) RSM 的重要性采样 RSM 的应用与缺陷 Screen Space Ambient Occulsion(SSAO) SSAO Blur ...
- SVO实时全局光照:Sparse Voxel Octree based Global Illumination (SVO GI)
功能已实现,初步集成进来,暂未进行重度优化.但GI的效果已很明显.这里特地给出了开启实时GI前后的效果对比,比对场景如下:1.只有直接光照(方向光源)的场景.2在直接光照(方向光源)基础上开启了实时G ...
- SVO实时全局光照优化(里程碑MK2):Sparse Voxel Octree based Global Illumination (SVO GI)
自主实现的实时渲染引擎,对标对象ue4/ce5,超越u3d/klayge.MK2版本侧重于质量与速度的均衡,以下上传示范均为实测截图,均为全分辨率(网页上显示缩小了)1080p/60fps.
- SVO实时全局光照:中等规模场景的GI实现
RTGI人生成就点unlocked! 快速集成DR+AO+SVO GI,针对中等场景粒度,初步具备全功能,暂未重度优化.附测试对比图.
- 全局光照:光线追踪、路径追踪与GI技术进化编年史
全局光照(Global Illumination,简称 GI), 作为图形学中比较酷的概念之一,是指既考虑场景中来自光源的直接光照,又考虑经过场景中其他物体反射后的间接光照的一种渲染技术. 大家常听到 ...
- Unity 5 中的全局光照技术详解
貌似是某位好人翻译的 https://unity3d.com/cn/learn/tutorials/topics/graphics/unity-5-lighting-and-rendering#rd? ...
- Unity 5 中的全局光照技术详解(建议收藏)
本文整理自Unity全球官方网站,原文:UNITY 5 - LIGHTING AND RENDERING (文章较长,请耐心阅读)简介全局光照,简称GI,是一个用来模拟光的互动和反弹等复杂行为的算法, ...
- Unity 5 全局光照GI与新的烘焙系统初探
http://blog.csdn.net/sparrowfc/article/details/45650013 GI是啥 Realtime GI,实时全局光照,听上去就是一个非常高大上的词,但是越高大 ...
随机推荐
- Kubernetes入门实践(环境搭建)
容器技术只是解决了运维部署工作中的一个很小的问题,在现实生产环境中,除了最基本的安装,还会各式各样的需求,比如服务发现.负载均衡.状态监控.健康检查.扩容缩容.应用迁移.高可用等等.这些容器之上的管理 ...
- java LocalDateTime的使用
1.LocalDateTime的基本使用 //获取当前时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println( ...
- BugKu_never_give_up
if(!$_GET['id']) { header('Location: hello.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b= ...
- Linux中重定向应注意的事情
引言 你是否见过bash ... 2>&1 1>file.txt的写法? 还没发现这样的写法有什么问题? 那么恭喜你, 看完本文你又将学会一个新知识! 重定向的错误用法 以引言中命 ...
- 数据库定时备份winserver2012篇
目录 1 序言 2 任务计划相关知识点介绍 2.1 任务计划 是什么? 2.2 批处理文件 2.2.1 批处理文件简介 2.2.2 批处理常用命令介绍 3 各个数据库备份脚本 3.1 Oracle数据 ...
- 2022-04-27:用go语言重写ffmpeg的remuxing.c示例。
2022-04-27:用go语言重写ffmpeg的remuxing.c示例. 答案2022-04-27: ffmpeg的remuxing.c是一个用于将多媒体文件从一种容器格式转换为另一种容器格式的命 ...
- 2021-08-14:给定两个字符串S和T,返回S的所有子序列中有多少个子序列的字面值等于T。
2021-08-14:给定两个字符串S和T,返回S的所有子序列中有多少个子序列的字面值等于T. 福大大 答案2021-08-14: 样本对应模型. 时间复杂度:O(N^2). 空间复杂度:O(N^2) ...
- python 环境下使用PIP 报错的解决方法
最近做一个小程序项目,使用djangorestframework,安装restframework 出现错误,安装环境Python2.7:出现错误如下: "UnicodeEncodeErro ...
- How to use the shell command to get the version of Linux Distributions All In One
How to use the shell command to get the version of Linux Distributions All In One 如何使用 shell 命令获取 Li ...
- v8 study
v8环境搭建看这里 现在的v8采用的是Ignition(JIT生成) + TurboFan(优化) v8调试 安装pwngdb git clone https://github.com/pwndbg/ ...
