「高质量实时渲染」实时环境光照
什么是环境光照?环境光照到底是干什么的呢?
环境光照其实就是说用一张图来记录我往任何一个方向看到的光照,这里隐含一个概念,认为光照来自无限远。
一般有两种存储方式:Spherical Map 和 Cube Map。
问题来了,给你一个环境光照,有一个物体在房间中间,在不考虑遮挡的情况下,如何计算着色呢?(这种操作在工业界被叫做 IBL [基于图像的光照 Image-Based Lighting])
1. 环境光着色
着色当然是解渲染方程! \[ L_o(p, \omega_o) = \int_{\Omega+}L_i(p, \omega_i) f_r(p, \omega_i, \omega_o) \cos\theta_{i} \mathrm{d} \omega_i \] 通用解法 —— 蒙特卡洛积分,但是这样需要大量的样本,所以非常的慢。
所以有没有其他方法,能更快的解决呢?
我们观察这个公式,积分里面是两个函数相乘,如果 BRDF 是 glossy 的,那么 lobe(图中蓝色部分)覆盖的范围就很小,如果 BRDF 是 diffuse,那么它是 smooth 的(变化不大)。
图1 观察渲染方程
而 BRDF 的这两个条件正好能满足我们之前提到的公式的条件,即安全地把 lighting 项拆出来! \[ L_o(p, \omega_o) \approx \dfrac{\int_{\Omega_{f_r}}L_i(p, \omega_i) \mathrm{d}\omega_i}{\int_{\Omega_{f_r}} \mathrm{d}\omega_i} \cdot \int_{\Omega+} f_r(p, \omega_i, \omega_o) \cos\theta_{i} \mathrm{d} \omega_i \] 这个拆分能够干什么呢?其实相当于是对 lighting 做一个 filtering,也就是说,可以先预计算环境光不同大小的 filter。那做这个 prefiltering干什么呢?
1.1 The Split Sum:1st Stage
可以观察下面左边的图,左边的图是看向某个着色点, 它的 BRDF 是某种 lobe,如果想要精准地算出来,就需要采样 BRDF 的 lobe,然后采样后的值做个加权平均就是着色点的值了。这样是不是就好像先把环境光做了一个 filtering,然后直接查询即可。
图2 The Split Sum:1st Stage
图1 点乘与叉乘作用
1.2 The Split Sum:2nd Stage
上面式子中第二项仍然是一个积分,那么应该怎么做呢?
想法:预计算各个变量(roughness、color)等所有可能的组合,但是我们需要一个很大的内存来存储。
怎么解决呢?想办法提取出关键的变量。
图3 The Split Sum:2nd Stage
图4 The Split Sum:2nd Stage
2. 环境光下的阴影
通常,对于实时渲染来说,环境光下的阴影是非常困难不可能做到的。
如果把环境光当成多光源问题,这样 Shadow Map 的开销就会与光源个数呈线性增长。
如果把其当成一个采样问题,那么可见项 Visibility 会变得特别复杂,且 V 也不能简单地从渲染方程中拆出。
工业界解决方法:从最亮的光源下生成一个(或者稍微多点)阴影。
3. 频率、滤波、基函数
任何 乘积的积分 可以认为是一种滤波:\(\int_{\Omega}f(x)g(x) \mathrm{d}x\)
- 低频 = 光滑的函数 / 很小的改变
- 积分结果的频率由 \(f(x)\) 和 \(g(x)\) 中的最低频率决定
基函数:可以用于表示其他函数的一系列函数集合。\(f(x) = \sum\limits_{i} c_i \cdot B_i(x)\)。傅里叶函数就是一系列基函数。
4. 球谐函数
球谐函数是什么?是一系列二维的定义在球面上的基函数 \(B_i(\omega)\),可以类似于一维上的傅里叶函数。
图5 球谐函数
\(l\) 代表阶数,第 \(l\) 阶拥有 \(2l + 1\) 种频率,阶数越高描述的频率越高,对应的基函数数量越多。
每一个 SH 基函数都是利用一个勒让德多项式写的。
那么问题来了,如果有一个二维的球面函数,如何把它展开成用球谐函数表示?
求球谐函数前面的系数(求系数的过程又叫投影)我们用如下公式求解: \[ c_i = \int_{\Omega} f(\omega)B_i(\omega) \mathrm{d}\omega \] 预过滤 + 单次查询 = 非过滤 + 多次查询!