当光线打到一个物体上时,靠近光源的地方会显示物体原本的颜色的亮色,当物体比较光滑时还会出现高光,距光源越远就会越暗,直至变的一片漆黑,当然现实生活中物体背光面并不会是一片漆黑,光线还会在空间中的其他物体上进行多次反射,间接的打到物体的背光面。
如何在程序中模拟光照呢?玩游戏的可能听说过光线追踪,但是光线追踪太过复杂,并且需要进行大量计算(想玩光追游戏还得有个高端显卡:)),所以一般运用在离线渲染中。本文介绍的是一种简化过后的模型 —— Blinn-Phong 反射模型。
Blinn-Phong 反射模型
Blinn-Phong 反射模型最早由 Phong 提出,后由 Blinn 对其做出改进。该模型不但计算效率高并且和物理事实足够接近,可以得到不错的效果。
下面会使用一些向量简写,L 表示光照向量,N 表示法线向量,E 表示观察向量,H 表示半角向量。
环境光反射
现实世界中一个物体没有直接光线照射的地方我们也能看见。这是因为有很多光线通过墙壁或地面等物体的经过多次反射最终打到物体的背光面,然后从背光面反射到我们的眼镜中,这才让我们可以看见它。
要准确的计算一个点的环境光是非常困难且复杂的,Phong 反射模型提出了一个简化的方式,可以快速计算出一个点的环境光。它假设一个点的接收的环境光永远都是相同的是一个常量,可以用下方公式表示。
ambient = ambientColor * ambientLight
一个物体的环境光反射就等于它的环境光系数乘以来自环境的光。
漫反射
当光线打到一个粗糙的表面时光线会被散射到各个方向,一般反射到各个方向上的光线强度并不像。
Phong 反射模型假设光线被均匀的反射到各个方向的强度都相同,这样无论什么方向观察物体的亮度都一样。
相同光以不同的角度照射到物体表面时,表面接收到的能量会不一样,比如中午时太阳光直射到地面,地面接收到的能量就特别多,旁晚时太阳光斜射到地面,地面接收的能量也就越少。根据 Lambert 定律可以知道,物体表面的接收的能量和表面的法线与光线的夹角有关。
energy = L · N = cos(Theta)
可以表示成单位光线向量与单位法线的点乘。因为两个都是单位向量,所以最终的结果就等于 cos(光线与法线的夹角)
。
表面的能量不仅与夹角有关,还与光源到物体之间的距离有关,距离越远接收到的能量也就越少。一般使用一个二次函数表示能量的衰减,因为这样更能准确模拟现实生活中的光的衰减。
Attenuation = 1 / (a + b * d + c * d^2)
其中 d
是距离,a
b
c
应该取什么值,可以参考文章末尾文献 5,可以用来实现比较真实的效果。
最终漫反射的公式,如下
diffuse = (1 / (a + b * d + c * d^2)) * max(L · N, 0) * diffuseColor * diffuseLight
上面公式表示物体的漫反射与光到达物体表面的能量和表面接收的能量,物体的材质和光线的属性有关。它将上面提到的两个公式直接相乘,其中使用了 max
函数,是因为如果点乘为负数时表示光线从下方打到物体表面。
镜面反射
当我们以一定角度看一个比较光滑的物体时,可以看见物体表面的高光。这是因为观察方向和镜面反射方向非常的接近。
参考漫反射,我们可以通过镜面反射向量与观察向量之间的夹角来计算物体的高光,Phong 反射模型就是这样计算的。
Blinn 就在这里提出了计算高光的优化,他提出了半角向量,通过半角向量我们可以很轻松的计算物体的高光。这就是为什么称作 Blinn-Phong 反射模型。
半角向量等于光线向量加上观察向量。
通过半角向量与法线的夹角来计算物体的高光,如果我们观察的方向与镜面反射光的夹角越接近,那么半角向量与法线之间的夹角也就越小。
specular = (1 / (a + b * d + c * d^2)) * pow(max(N · H, 0), shininess) * specularColor * specularLight
同样我们加上距离的衰减,这里还进行求幂运算,这是因为现实生活中物体的高光一般都很小,观察方向与反射方向夹角稍微一大就看不见高光。
最终我们将这三个颜色加起来就是物体最终现实的颜色了。
color = ambient + diffuse + specular