在上一篇我们实现了折射,也假设了反射和折射的比重是恒定的。但现实上,这个比重与材质和入射角相关。
题图是我 2006 年在九寨沟拍摄的相片。相片底部,视角较接近垂直,可以看到较清晰的水下景物,倒影较暗;而在远处,视角较与水面平行,则几乎只看见倒影,难以看到水下景物。
你现在也可以把一个马克杯装满水,放在屏幕前观察水面。如果眼睛接近水平面,就可以清楚地看到屏幕倒影;如果视点往上移,那么屏幕倒影变暗,更多看到杯里的颜色。
这个现象称为菲涅耳反射(Fresnel reflectance)。奥古斯丁·菲涅耳(Augustin Fresnel, 1788-1827)为法国物理学家,注意 Fresnel 为法语姓氏,s 是不发音的。
1. 菲涅耳方程
菲涅耳方程(Fresnel equation)描述了光线经过两个介质的界面时,反射和透射的光强比重。参考下图的符号:
当中 为反射比,因能量守恒,透射比为
。
对于电介质(dielectric)而言,菲涅耳方程为:
和
分别对应入射光的 s 偏振(senkrecht polarized)和 p 偏振(parallel polarized)所造成的反射比。图形学中通常考虑光是无偏振的(unpolarized),也就是两种偏振是等量的,所以可以取其平均值:
那么,给定入射角、透射角的余弦,以及两个介质的折射率,就可简单实现:
float fresnel(float cosi, float cost, float etai, float etat) {
float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost);
float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost);
return (rs * rs + rp * rp) * 0.5f;
}透射角实际上是使用斯涅尔定律从入射角得出的,所以假设折射率是常数,菲涅耳反射比是入射角的函数。下图展示了光线从空气射向不同材质时的菲涅耳反射比(来自[1] P. 233,为菲涅耳反射比):
在趋向 入射角时,不论什么材质反射比都趋向 1。
另外,导体(conductor)和半导体(semiconductor)的菲涅耳方程会更复杂一些,而且不同对波长的影响较大(上图中红色和紫色曲线分别对应 RGB 三种波长),我们稍后再讨论。
2. 修改实现
这个函数用于取代原来恒定的 ,我们原来的追踪代码是这样的:
if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) {
/* ... */
if (r.eta > 0.0f) {
if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry))
sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1);
else
refl = 1.0f; // Total internal reflection
}
/* ... */
}当有折射发生时,我们利用点积计算入射角和透射角的余弦,然后用 获取反射比。和调用
时相似,我们要考虑光是从空气(
)进入物体,还是相反:
if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) {
/* ... */
if (r.eta > 0.0f) {
if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) {
float cosi = -(dx * nx + dy * ny);
float cost = -(rx * nx + ry * ny);
refl = sign < 0.0f ? fresnel(cosi, cost, r.eta, 1.0f) : fresnel(cosi, cost, 1.0f, r.eta);
sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1);
}
else
refl = 1.0f; // Total internal reflection
}
/* ... */
}我们比较一下,上一篇使用恒定反射比和本篇使用菲涅耳方程的渲染结果:
比较明显的区别是,光线垂直射向凸透镜和凹透镜时,使用恒定反比射会产生很亮的反射光斑,而使用菲涅耳方程的反射光斑就会暗得多,大部分光能穿过了透镜。
3. Schlick 近似
鉴于导体的菲涅耳方程较复杂,Schlick [2] 提供了一个近似的函数:
此函数只需要对材质提供光线垂直反射时的 值,就能近似地计算出不同入射角旳菲涅耳反射比。下图展示此近似函数(虚线)与菲涅耳方程(实线)的比较(自 [1] P. 234):
对于电介质,我们可以使用 (2) 计算出 :
对于导体,我们就需要提供 的值(可能需要多个频率,如 RGB)。另一点要注意的事,从低折射率材质到高折射率材质时,要使用
:
float schlick(float cosi, float cost, float etai, float etat) {
float r0 = (etai - etat) / (etai + etat);
r0 *= r0;
float a = 1.0f - (etai < etat ? cosi : cost);
float aa = a * a;
return r0 + (1.0f - r0) * aa * aa * a;
}这个函数采用和 相同的接口,可以在例子中替换。但如果要支持导体,则要改变接口。
这个例子中用 Schlick 近似并没有肉眼能分辨的差异,就不显示渲染结果了。
4. 结语
我们依据菲涅耳方程,以入射角及材质特性推断出光线的反射比和透射比,这样更接近真实世界的情况。
Schlick 近似被广泛应用在实时的基于物理渲染(physically based rendering, PBR)中,除了运算简单快捷,以 作为参数更容易让美术理解──白光直射材质所反射的颜色。用物理量(如本文未提及的波阻抗和磁导率)作为参数的话就很不友好。
本文的代码位于 fresnel.c。
参考
[1] Akenine-Möller, Tomas, Eric Haines, and Naty Hoffman. Real-time rendering, Third Edition. CRC Press, 2008.
[2] Schlick, Christophe. "An Inexpensive BRDF Model for Physically‐based Rendering." Computer graphics forum. Vol. 13. No. 3. Blackwell Science Ltd, 1994.
更新1:谢
指出,公式 (4) 右侧需平方,已修正。