一、前言
到目前为止的前几篇文章已经向大家介绍了从MVP变换到屏幕映射到光栅化的过程,但是仅仅这些还不能让我们很好的模拟真实的世界,究其原因是上面的所有过程都没有涉及光线的运算,而缺少了光也就缺少了明暗的变化,导致渲染出来的结果并不真实。这就需要我们本篇所讲的内容:着色:引入明暗和颜色的不同。而闫令琪老师的GAMES101中把着色定义为引入不同的材质。材质本质上就是和光线的作用结果,也就是说不同的材质对光线有不同的作用结果,也就导致了我们看每种不同材质的物体的感觉并不相同。
二、Blinn-Phong模型
1.总览
Blinn-Phong 反射模型最早由 Phong 提出,后由 Blinn 对其做出改进。该模型不但计算效率高并且和物理事实足够接近,可以得到不错的效果。Blinn-Phong和Phong的区别我们会在待一会介绍给大家。无论是Phong还是Blinn-Phong都有一个基础的思想,即任何光照都分为三个部分:漫反射部分,高光部分,环境光部分。由此三种光照来模拟任何物体表面的光照。
Photo credit: Jessica Andrews, flickr
定义:
着色点始终在物体的局部规定为平面,
观测方向v,法线方向n,光线方向l,以单位向量表示
物体表面相关属性:color,shininess(这里不是亮度)
同时只考虑着色点的局部作用,不考虑阴影(shading ≠ shadow)
2.漫反射部分
在漫反射中,光线会被均匀的反射到各个方向
但是实际上,这只是理想情况,实际中,光线在传播过程中会有各种各样的能量损失,包括角度和其他因素。
上图揭示了,当光照和平面呈一定角度时候,光线的所有能量并不能都打到平面上,而是会损失一部分,而我们之前已经定义出了法线和光线的单位向量,不难发现,两者夹角越小时,损失的能量越少,反之损失能量越多。我们定义的单位向量的点积刚好可以表示出这个关系,也就是说l·n越接近1,光线越接近于与表面垂直,那么这个表面应该越亮,反之则越暗。
当然光线能量的损失不仅仅有角度这一因素,我们再来看下面这张图:
这张图表示,一个点光源散发出的光每一段距离的传播,他的能量都集中在一个球壳上,而随着半径r,也就是传播距离越大,那么球壳的表面积增大,如果总能量不变的话,分布于每块单位面积的能量就会减少,而表面积中有r的平方,那么每层的单位面积上的能量应该和r的平方呈反比。这个过程称为衰减。也就说接受光线的点离光源越远,那么接受的能量越少。
那么综上,我们可以得出漫反射的表示方法:
其中kd为定义的漫反射系数,为1表示不吸收任何能量,反射出白光(如果光源是白色),为0则吸收所有能量,显示出黑色,如果把它定义为RGB,那么就可以定义对不同颜色的吸收率,也就可以显示出不同的颜色。
其中max把cos的值从[-1,1]截断到了[0,1],因为负数在这里没有意义。(鄙人的补充:这里其实是兰伯特模型和半兰伯特模型的区别,这里采用半兰伯特在美术上显得更通透而不是兰伯特的那种背光面完全死黑)
3.高光部分
接下来我们说高光部分,通过生活经验和一些物理常识不难得知,当我们的视角方向和镜面反射光的方向越接近时,我们看到的高光会更强一些,这给了我们启发。
也就说,我们用可以计算 l 的反射方向 R 和我们视角 v 的cos大小来得到我们高光的大小,而这也就是我们Phong模型与Blinn-Phong模型的区别,这套方案是Phong模型中计算高光的办法,而在Blinn-Phong中我们对高光计算进行了优化如下图:
我们引入半程向量h:视角方向v和光照方向l的角平分线方向的单位向量。
ks为镜面反射系数,通常认为是白色。
不难看出,当h和法线n越接近时,我们的v也就和R(l的反射方向)越接近。我们用这种近似方法代替Phong中的R·v,而这就是Blinn-Phong的高光计算方法,它减少了计算时候的性能损耗。
那么为什么要在余弦上加一个p次方呢?因为余弦函数本身的变化率不够剧烈甚至接近于线性,导致如果真的单独用余弦来描述的话,我们会看到一个很大范围的高光,所以加上p次方之后,可以根据实际情况进行调节高光范围的大小,p越大,高光越集中,高光范围越小。下图揭示了这一规律:
4.环境光部分
环境光是间接光照,例如一个物体靠在一个红色的墙边,那么红色的墙反射出的红光也会照到该物体,但是这并不是光源的直接照射,所以被称为间接光照。想要精确计算这么复杂的光是很难的,在Blinn-Phong模型中对环境光进行了理想化的简化假设即:任何一点接受的环境光都是相同的,且环境光来自四面八方,强度Ia。也就是我们把环境光假设成了一个常数,与我们定义的各种单位向量没有任何关系。
5.总结
把我们上面说到的三部分都加在一块,就构成了Blinn-Phong模型,如上图。
三、着色频率
上图的三种球的模型是相同的,但是着色频率不同,第一幅图对面着色,第二幅图对每个顶点着色,而第三幅图对每一个像素着色。这说明了,我们的着色频率越高,效果越好。
上面给出了三种不同的着色频率:Flat Shading,Gouraud Shading, Phong Shading。也就分别对应了我们说的:
对面着色,对顶点着色中间进行插值,对顶点像素中间的像素插值法线进而对每个像素着色。
但是需要注意的是,并不是说Flat这种低频率的着色频率就无法达到比较好的效果如下图所示:
当球的模型足够光滑,也就是面数足够多的时候,即使是对每个面着色,也可以达到比较好的效果。所以要根据实际情况选择合适的着色频率才能有效的减少性能消耗和达到比较好的着色效果。(例如若面数很多,场景很复杂,那么Flat Shading会造成巨大的性能消耗,而Phong Shading基于像素的着色频率则会节省很多性能)
逐顶点的法线怎么求呢?
这里分为两种情况:知道的基础模型/其它情况
如果知道了模型的样子,比如一个球,那么法线自然就是从球心出发向各个方向,但是实际情况往往复杂的多,于是人们发明了一种方法,用临近该点的各个三角面的法线进行加权平均,权重的大小即为该面所占各个面积之和的大小。如下图所示:
那么如果知道了两个顶点的法线,顶点之间的逐像素的法线怎么求呢?
这里需要用到重心坐标进行插值(求出的法线都要做归一化,因为它们都是单位向量)
这里因为篇幅有限,重心坐标我们下一篇进行讲解。
参考:
Lecture 07 Shading 1 (Illumination, Shading and Graphics Pipeline)_哔哩哔哩_bilibili
Lecture 08 Shading 2 (Shading, Pipeline and Texture Mapping)_哔哩哔哩_bilibili