webGL Lighting

-- settled by horizon
冯氏反射模型(Phong Reflection Model)
  1. 一种是从特定方向射入并只会照亮面对入射方向的物体,我们称之为平行光(directional light)。
  2. 另一种光是来自所有方向并且会照亮所有物体,不管这些物体的朝向如何,我们称之为环境光(ambient light)。当然在真实世界里,这只是平行光照到其他物体上,比如空气、灰尘等等,然后反射出来的散射而已。但是在这里,我们需要把它单独作为一个光照模型列出来。
    1. 漫反射(Diffuse):无论光的入射角度如何,都会向所有方向发生反射。反射光的亮度只和光线的入射角度有关,与观察角度无关。光线越平行于物体表面,则反射光越弱,表面越暗;光线越垂直于表面,反射光越强,表面越亮。漫反射是我们通常想到一个物体受到光照时需要首先想到的。
    2. 镜面反射(Specular):这就像镜子一样,反射光将按照和入射角相同的角度反射出来。这种情况下,你看到的物体反射出来的光的亮度,取决于你的眼睛和光反射的方向是否在同一直线上;也就是说,反射光的亮度不仅与光线的入射角有关,还与你的视线和物体表面之间的角度有关。镜面反射通常会造成物体表面上的“闪烁”和“高光”现象,镜面反射的强度也与物体的材质有关,无光泽的木材很少会有镜面反射发生,而高光泽的金属则会有大量镜面反射。

冯氏反射模型引申了这个四步走的光照系统,首先所有的光线都有以下两个属性:

  1. 发生漫反射光的RBG值。
  2. 发生镜面反射光的RGB值。

其次所有材质都有以下四个属性

  1. 反射的环境光RGB值
  2. 反射的漫反射光RGB值
  3. 反射的镜面反射光RGB值
  4. 物体的反光度,它决定了镜面反射的细节

对于场景中的每一点,它的颜色都是由照射光的颜色、材质本身的颜色和光照效果混合起来的。所以,根据冯氏反射模型,为了解决场景中的光照,每条光线我们都需要知道两个属性,每个物体表面上的点都需要4个属性。环境光应当是自然的,而不是特定的光线,但我们依然需要找到一种方法来储存整个场景中的环境光;有时可以用最简单的方法,就是为每个光源设置一个环境等级,然后把它们都放到一个单一项中。

好了,我们有了以上的预备知识,我们就能计算出环境光、平行光和镜面反射光照在任何一个点上的颜色,然后再把它们组合到一起,就得到了最后的颜色值。下面这幅图清晰的解释了我们的工作原理。而我们所有的着色器需要做的就是分别计算出在环境光、漫反射光和镜面反射光下每个顶点的红、绿、蓝的颜色,然后组成RGB值,再组合到一起,最后输入结果。

最后,只考虑一种最简单的漫反射光,那就是平行光。下面我用图表来解释一下。

从一个方向上来的光可以分为两种。一种是简单的平行光,来自于同一个方向的平行光束穿越整个场景。另一种是点光源,来源于场景内的一个点发出的光线,也就是说每个地方的光线角度都不一样。

对于简单的平行光来说,当光线打到物体表面的顶点上(图中的A点和B点),入射角永远都是相同的。想一下太阳光,光线都是平行的。

相反,对于点光源,A点和B点的入射角是不同的。A点差不多是45°,而B点则接近0°,也就是说B点的入射光线垂直于物体表面。

这也就意味着对于点光源,我们需要为每个顶点都计算出各自不同的光线入射角度;然而对于平行光,我们只需要使用一个固定的角度。这就使得点光源变得有一些复杂(to be continue ...)

这样我们就把问题精炼了。我们知道我们场景中的所有光线都会来自于一个固定的方向,而且这个方向对于每个顶点来说都是不会改变的。也就是说我们可以把它放到uniform变量中,然后提供给着色器来调用。我们同样知道每个顶点上的光照效果取决于光线的入射角度,所以我们需要找到一个可以代表物体表面朝向的东西。对于3D几何体,最好的办法就是指定顶点所在表面的法线向量,这个向量允许我们用3组数字表示出物体表面的朝向。(在二维世界中我们可以同样使用切线来达到这一目的,但是在三维世界中,切线的垂线是指向两个方向的,所以我们要用两个向量来表示它,而表示法线我们使用用一个向量就可以了。)

除了法线之外,在像着色器写入代码之前我们还需要最后一样东西。我们指定了顶点平面的法线向量,还有用来表示光照方向的向量,我们还需要计算出物体表面漫反射了多少光。这与这两个向量之间角度的余弦值成正比。当法线向量与光照方向向量的夹角是0度的时候(也就是说,光线完全照射到物体表面,光线方向90°垂直于物体表面),我们可以看做物体反射了所有的光;当夹角为90度的时候,没有任何光线被反射;当夹角处于0到90度之间时,应当符合余弦曲线。(如果当角度大于90度时,根据我们的理论会得出一个负值的反射光,这显然是很扯淡的)

计算这两个向量夹角的余弦值并不是什么复杂的计算,如果它们两者的长度都是1,那我们只要使用这两个向量的点积即可。点积运算是内置于着色器的,我们只要使用这个叫做dot的函数即可。