Intro
Lightmass 是 Unreal Engine 里用于烘焙静态全局光照的工具. 全局光照 (Lightmass) 创建具有复杂光交互作用的光照图, 例如区域阴影和漫反射. 它用于预计算具有固定和静止运动性的光源的照明贡献部分. UE4 Lightmass 手册.
Lightmass 借用 PhotonMapping 算法的思想, 实现了自己的全局光照工具. 正常的 PhotonMapping 是一种渲染方法, 尤其是其 render-pass, 借助 PathTracing 最终渲染出某一视角下的场景. 而 Lightmass 更多的是为了产生光照贴图, 在实时渲染过程中的全局光照提供数据. 这也导致 Lightmass 的后半部分与正常的 PhotonMapping 有一定的差别.
个人认为 Lightmass 的代码之所以难以阅读, 一是过程中涉及的类和函数繁多, 二是用于保存中间结果的数据结构与其产生的作用难以建立联系, 三是流程中除了核心逻辑外, 穿插着很多为了优化运行效率的部分.
繁杂的处理过程和数据流动会分散我们的精力, 干扰整体分析. 所以下面先将核心流程抽取出来, 并隐藏各种复杂的实现细节, 只关注输入输出, 让我们可以在有一个整体的理解之后再尝试深入研究具体细节.
核心流程
- 发射光子(直接, 间接), 计算光子的辐照度并建立光子图
- 缓存物体表面的光照信息
- 评估几何表面的光照(直接光照, 间接光照)
- 将光照结果编码为光照贴图
简单说明
"发射光子"之后我们会得到三个光子图以及一个辐照度光子图. 三个光子图分别是 DirectPhotonMap, FirstBouncePhotonMap, SecondBouncePhotonMap. 分别表示直接光照光子图, 间接光照第一次反弹光子图, 间接光照其余反弹光子图. 辐照度光子图在一般情况下近似为上面三张光子图的合集, 用来保存场景中光子的辐照度, 为最后的光照评估提供数据.
由于通过不同方式产生的光子, 在影响范围, 光照贡献等很多方面都存在差异, 所以在发射光子的时候直接进行区分对后续流程非常友好.
PS: 这里所谓的"光子图"实际上都是八叉树
光子辐照度评估公式: 光子辐照度 = 直接光子辐照度 + 第一次反弹辐照度 + 第二次及后续反弹辐照度.
其中直接光子辐照度默认不使用光子评估(公式中对应项的值不被累加到结果中), 后面的项是否生效与反弹次数的设置有关. 最后光子的辐照度根据其附近的光子的 Power 经过 ConeFilter 计算得出.
为什么要使用表面缓存? 我们对场景的一个基本假设就是场景中物体表面的光照是连续且平滑过渡的. 这让我们可以使用少量样本采样其周围环境, 再通过插值的方式获取完整的全局光照信息. 可以看出这个过程中存在很多的均值操作需要计算, 每个离散位置的光照都会多次参与运算. 所以提前缓存部分数据是明智且必要的操作.
Lightmass 中的直接光照和阴影默认通过 RayTracing 方式计算, 间接光照则使用 Final Gather 的方法计算. 在这之后将经过球谐编码的数据保存到 Lightmap 中即可在实时渲染的时候使用. (详情请移步系列后续文章)
参考文献
Jiff:Lightmass分析之 经典Photon Mapping算法介绍
Jiff:LightMass源码分析之光子追踪实现
Jiff:LightMass源码分析之光照评估