Next Event Estimation

Next Event Estimation (NEE) 是一个常被提及的概念,却鲜有中文资料介绍。受这篇文章启发,我姑且将 NEE 定义为「提前计算直接光照」。这一概念常常和 MIS 等混淆,特此整理一下。

算法

首先还是列出渲染方程

\[ L(x\to\omega_i) = L_e(x\to\omega_i) + \int_\Omega L(\omega\to x)f_s(\omega\to x \to \omega_i)\cos\theta d \omega \]

渲染方程带有递归性质,实质上是直接光照 \(L_e\) 的累加

\[ L(x^0\to\omega^0)=\sum_i K_i L_e(x^i\to\omega^i) \]

其中上标代表一条追踪路径 \(x^0\to\omega^0\to x^1\to \omega^1\to\cdots\) ,到这里其实就可以写出一个简单的路径追踪函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Li(dir):
throughoutput = 1
radiance = 0

while(before max depth)
x = scene.Intersect(dir)

if (x is on a light)
radiance += Le(x, dir) * throughoutput

dir, pdf, cos, bsdf = SmapleNextDirection()
throughoutput *= bsdf * cos / pdf

return radiance

这里采样下一条路径的方法是任意的,可以根据材质等等。思路很简洁,但存在一个问题:当且仅当采样的路径击中光源表面时,才会对该光线的结果产生贡献。在光源较少的场景里这并不是一件容易的事!当然只要光线足够多,迭代足够深,总会得到收敛的结果,但也会导致较大的方差。

换句话说,造成该现象的原因是「计算光源贡献的时机太迟了」,非要到直接击中时才计算。事实上,我们可以将交点受到的光照拆成两部分:光源直接照射和其他表面的散射

\[ L(\omega\to x)=L_e(\omega\to x) + L_s(\omega\to x) \]

把渲染方程改写成

\[ \begin{aligned} L_s(x\to\omega_i) =&\int_\Omega L_s(\omega\to x)f_s(\omega\to x \to \omega_i)\cos\theta d \omega \qquad\text{(indirect light)}\\ +&\int_{\Omega_e}L_e(\omega\to x)f_s(\omega\to x \to \omega_i)\cos\theta d \omega\qquad\text{ (direct light)} \end{aligned} \]

这样改写以后,和原来的渲染方程形式上几乎一致,区别在于 \(L_e\) 项变成了一个关于 \(L_e\) 的积分。这样,每次击中表面时,都会计算「受到的直接光照的积分」,即使是小光源也总会产生贡献。从「表面发出的光」到「受到的直接光照积分」,将后续直接光的计算提前,这就是我对「Next Event」的理解。

计算上式是很简单的,和前述原始渲染方程的计算方法完全相同,只不过把 \(L_e\) 换成一个可计算的积分。第二项直接光积分更常见的形式是

\[ \begin{aligned} \int_{A}L_e(p\to x)f_s(p\to x \to \omega_i)G(x,p)dA\\ G(x,p)=V(x\leftrightarrow p)\frac{\cos\theta_1\cos\theta_2}{\lVert p-x\rVert^2} \end{aligned} \]

采样域转化到面上计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Ls(dir):
throughoutput = 1
radiance = 0

while(before max depth)
x = scene.Intersect(dir)

pdf_direct, bsdf_direct, emittance, geo_term = SampleLight()
direct_radiance = bsdf_direct * emittance * geo_term / pdf_direct
radiance += direct_radiance * throughoutput

dir, pdf, cos, bsdf = SmapleNextDirection()
throughoutput *= bsdf * cos / pdf

return radiance

注意一个细节,这里计算的是 \(L_s\) 而非 \(L_i\) ,如果从相机发出的光线所交的第一个表面就有光照 \(L_e\),要加到结果上。因为第一个表面的直接光照已经没法再提前计算了。

和多重重要性采样(MIS)的关系

可能会有人和我有过一样的疑问:这不就是 MIS 中的对光源采样吗,何必专门去说?

事实上,我所做的不过是将 \(L\) 拆成 \(L_s\)\(L_e\) ,改写渲染方程的形式,得到 NEE 的计算框架。至于采样无非是计算积分的方式,和 NEE 本身并无关联,这是非常容易混淆的。

在 NEE 下应用 MIS 的计算对象是积分 \[ \begin{aligned} E(\omega)=&\int_{\Omega_e} L_e(\omega\to x)f_s(\omega\to x \to \omega_i)\cos\theta d \omega\\ E(p)=&\int_{\Omega_e}L_e(p\to x)f_s(p\to x \to \omega_i)G(x,p)dp \end{aligned} \] 可以将积分域延拓到 \(\Omega\) 上(没有直接光照的地方 \(L_e=0\)),再根据材质做重要性采样,并和光源重要性采样的结果加权平均。注意到根据光源采样和根据材质采样的采样域是不一样的,但并不影响结果的无偏性,有关 MIS 性质的证明可以参考这篇文章,结果是 \[ \hat{E}=W(\omega_s)\frac{E(\omega_s)}{pdf(\omega_s)}+W(p_s)\frac{E(p_s)}{pdf(p_s)} \]

可以复用样本 \(\omega_s\) 采样间接光积分。权重函数此处不予赘述。

为什么只对 NEE 中的直接光积分使用 MIS ?因为直接光积分的计算不存在递归,如果直接对原始渲染方程中的积分使用 MIS \[ \int_\Omega L(\omega\to x)f_s(\omega\to x \to \omega_i)\cos\theta d \omega \] 当然也可以根据光源和材质采样出两条路径,但这两条路径都需要递归计算,会导致光线数以指数级别增长,是不可接受的。


Next Event Estimation
https://adven00.github.io/2023/11/27/Next-Event-Estimation/
作者
Adven
发布于
2023年11月27日
许可协议