去年十月搬家到现在一直没有对 Nuo Model Viewer 做大改动。年前忙于布置新家。到新年前后稍感安定想增加些新功能,但越想越感觉已有的 ray tracing 代码缺乏数学依据,于是开始温习数学概念。
粗糙的实现
首先,考虑仅支持理想方向光源直射,以及理想纯漫反射材质 (Lambertian) 的 shading 公式 [1]:
这是两年前开始做 Nuo Model Viewer 时最先实现的公式。其实要分析很多细节才能保证完全正确的实现 —— 如何从模型材质描述中得到,如何处理 π 相关的系数,场景的光源描述如何与
相对应。对于 Wavefront OBJ 格式来说,几乎找不到直接阐述
Kd
参数和的关系的文档。因为当时只支持直接光照和理想方向光源,对这些细节的分析都偷懒了,在用户界面上放个 slider 解释为
,其中 k 为任意系数。如此一来
Kd
参数和的系数关系以及光源描述如何与
相对应都无需精确考虑,通过 slider 的取值进行补偿达到最终结果美观即可。
去年实现了几个基于 ray tracing 的功能:area light source 产生的软阴影;ambient 环境光 [2];自发光物体的光照。当时并未多想通过 slider 补偿系数的做法是否还成立。现在发现,要继续做新东西有必要彻底理解这些细节。接下两节先分析 shading 公式中具体系数的意义。然后解释实现 ray tracing 要注意什么。
光源的理解
引入 area light source 后,光线方向不再限于单一角度。直接光照公式变为积分形式(这里只考虑 Lambtian 材质,在所有方向上为常数):
若将理想方向光源应用于积分形式,即为对单一方向上的冲击分布积分:
这与一开始的简单公式非常类似,要让二者等价只需保证。下面就来看看
、
、以及
Kd
参数的关系。
材质的理解
在、
、和
Kd
中,的物理意义最明确,是材质表面反射能量比值的微分形式,其一般形式
定义如下:
现在来寻找的物理意义。如果去掉直接光照公式里的
,即认为其在各个方向均为 1,就得到了材质的 directional-hemispherical reflectance,对一般材质和 Lambertian 材质分别是:
按照这个定义,就是 Lambertian 材质的
。在《Real-Time Rendering, 3ed》里称其为 diffuse color。
现在看来,有两点证据说明 Wavefront OBJ 中的 Kd
就是:第一,
Kd
在很多文档中也被称为 diffuse color;第二,对物理真实材质来说,能量守恒定律要求 小于 1,而
Kd
的取值范围一般是 [0, 1] 区间 [3]。这两点都不能说是完全确定的证据,但 Wavefront OBJ 是个老旧的格式,其材质描述方面的正式文档很少,这大概是可以得到的最好解释了。
Monte Carlo 方法
前两节解释了 shading 公式的细节,现在回到原来的问题 —— 加入基于 ray tracing 的功能之后是否还能用 slider 补偿系数关系,需要做出什么改变?首先从 ray tracing 使用的 Monte Carlo 方法说起。用简单的一重积分来说明,若求积分 [4]:
可以在 [a, b] 区间内通过均匀随机分布取一组 sample ,通过它们取得近似的结果:
这里求和式中的单项被称为 Monte Carlo estimator:
把应用扩展到多重积分,如果用均匀随机分布取样求积分:
那么其 estimator 为:
现在可以想到,若 ray tracing 实现里涉及多重积分,且每重都包含一定的系数关系,就不一定能用界面上的 slider 进行补偿。下一节分析在 ray tracing 多重积分中的系数关系。
Monte Carlo 方法与 Ray Tracing
现在把 Monte Carlo 方法应用到光线的路径函数上。表面反射次数为 n 的光路上的传播函数为 [5]:
去年给 Nuo Model Viewer 加入环境光和自发光物体光照时,并没有认真保证代码准确反映上面的多重积分公式。相反,当时还是惯性思维地觉得凭 slider 就能补偿一切系数问题,所以凭感觉写了一个 estimator:
现在来看数学上正确的 Monte Carlo estimator,如果采用均匀随机分布:
如果只考虑 diffuse 效果:
它们都带有含 n 次方的系数。在一个场景的 ray tracing 中,每条路径的 n 可以不同,可能是随机遇到了光源 (自发光物体),也可以是 tracer 主动在某个长度上向光源直接采样。看起来 Nuo Model Viewer 的效果正确性令人担忧。不过 Nuo Model Viewer 并没有采用均匀随机分布,而是以 cosθ 成正比的概率分布做 importance sampling。那么正确的 estimator 应该是:
将 k 带入原式,得到:
居然恰好和最初乱写的一样!几个月没动代码,但总算搞清了其中的意义。
脚注:
- 见《Real-Time Rendering, 3ed》,p110。
- 不是基于 occlusion 计算的阴影,而是基于 ray tracing 直接计算 ambient lighting。
- 如果加入对 specular term 的考虑,并且类似的考虑
Ks
参数,则需要满足Kd
+Ks
< 1。这在绝大多数 OBJ 材质中也是满足的。 - 本节中的 f ( x ) 表示任意函数,注意不要和下文中的 BRDF 分布函数混淆。
- 见《Physically-Based Rendering, 3ed》,p867。这里把公式改写为 solid angle 形式,并且用嵌套括号强调了其递归模式。简单的乘法结合交换就可以变成单个多元函数的多重积分。