3.着色

本系列的主要目的是复盘并总结计算机图形学这个学科常用算法与原理,会从基本的数学理论讲起,同时会附带部分算法的代码实现,由浅入深,只需初中的数学基础就行。主要参考了GAMES101-现代计算机图形学入门这门课(具体引用见文章末尾),同时附加上自己的理解。有兴趣的也可直接看视频。

着色

通过上面的光栅化和深度缓存,我们已经能够把模型正确的渲染在屏幕上了。如下图所示,这是一排排的正方形。是不是看着总感觉缺了点什么

image-20220917141908477

没有明暗交替,没有阴影,没有现实生活中的材质感。我们想看到的应该是下面这样的才对

image-20220917142028925

这样就感觉真实了很多。再看看更复杂的案例

image-20220917142140757

可以看到上图中的瓷器表面显示的有明有暗,其中甚至还有反射出来的倒影。而且我们一眼能看出图中的杯子是陶瓷杯子而不是金属杯子,塑料杯子,这都是因为着色结果给我们的材质感。

因此着色不仅仅只考虑物体本身的颜色,还要考虑物体的材质,环境的光照等等。

在图形学中,我们可以把着色定义为在不同物体上应用不同材质的过程。因为不同材质和光产生的相互作用就会不同,例如反射率,折射等。和光的不同作用就会显示出不同的结果,因为我们能看见物体都是因为光线进入了人眼。

在研究具体的方案之前,我们先大致了解一些着色需要关注哪几个点

image-20220917142500489

首先是高光,这个我们生活中很常见,可以理解为在材质上经常能看到一块亮斑;

然后是漫反射。漫反射是指光打到物体上,然后被反射到四面八方的过程;

接着是环境光,上图中的杯子背部明明没有面对光源,但依旧能够被照亮,这就是环境光。

那么接下来,我们就围绕这三个方面来详细讲一下着色是怎么回事。(这其实就是大名鼎鼎的Blinn-Phong模型)

Blinn-Phong模型

漫反射

计算漫反射其实是个相对繁杂的过程,我们需要先理解反射的定义,然后算出光能的辐射,从而算出物体能接收到的光能,最后算出物体反射出去的能量。但是每一步都不难,加油看下去吧!

反射

首先来看看反射是怎么回事。为了方便分析,我们通常会将物体表面细分成一个个着色点,也可以理解成是物体表面每一块极小区域受到光照的结果。这样的好处就是,即使物体表面是曲面,我们也可以把一个着色点当作是一个平面,如下图:

image-20220917143830968

根据每个着色点,我们可以有如下一些定义:

  1. 既然是平面,那么我们就可以定义其对应的法线,即图中的 n 向量。
  2. 光源对应着色点的方向,即图中的 l 向量,可通过光源位置减去着色点位置然后归一化得到,若多个光源,则每个光源有一个对应的方向。
  3. 摄像机/人眼对应着色点的方向,即图中的 v 向量,可通过摄像机位置减去着色点位置然后归一化得到。
  4. 表面的一些属性,例如颜色(color),亮度(shininess)等等。

注:上面提到的三个向量代表的都是方向,因此它们都为单位向量

同时我们在考虑着色时,不需要考虑阴影,即不用考虑某个着色点的光照被其他物体遮挡的问题。我们只考虑该着色点本身的各个属性即可,这也是着色的局部性,因此着色过程中也不会产生阴影

Lambert的余弦定率

接着我们来看下不同方向的光照对于着色点(着色点面积大小固定)的影响。

假设我们把一束光离散成几根光线,那么可以发现当我们的着色点和光照方向形成不同的夹角时,所接受到的光线数量也有所不同。因此着色点在不同角度下得到的光照能量不同,所以着色点所显示的亮度也应该有所不同。如下图:

image-20220917144228873

从上图我们可以直观的看出光照角度对物体接受到的能量的影响。我们前面定义了光照方向为 l 向量,着色点方向即可以理解为法线方向 n 向量,因此它俩的夹角 θ (cosθ等于l和n向量的点乘)即可决定该着色点可以接收到多少的光照能量。

光能的传播

我们假设有一个点光源,它每时每刻会往均匀的往所有方向发射光。因为在同一个介质中,光速是相同的,因此假设在 t0 时刻点光源发射了光,那么在 t1 时刻,这些光的能量肯定都均匀的分布在一个球面上,如下图,展示的二维的情况:

image-20220917144739749

因为能量守恒的原理,我们假设在传播过程中能量不损失,那么某一时刻的光在传播时,它所围成的球面会越来越大,但是总的能量依旧不变,因此球面上一个固定面积的能量随着传播是越来越少的。

球的面积公式为$ S=4πr2 I/r2$ ,即成半径平方的反比进行衰减

计算着色点接收的光能

通过前面的介绍,我们就可以很轻松的计算出一个着色点接收到的光的能量。

image-20220917150847561

如图,接着我们定义着色点到光源的距离为 r,光源在半径为1时对着色点的能量为 I,那么该着色点能接收到的能量L为:

  • 代表光到达这个点的能量
  • 代表这个点接收了多少能量

这里多说一句,为什么要 呢?因为如果光源在着色点的背面,即 θ > 90° 的情况下,此时点乘为负数,我们就当做没有光照,取 0

光的颜色

通过物理的学习,我们知道可见光是由不同颜色的光波所组成的,物体之所以有颜色是因为对光线不同程度的吸收所导致的。

例如物体把所有光线都吸收了,那么它就是黑色,反之如果都不吸收,那么就是白色,而红色就是吸收了除了红色以为所有的光。

因此我们可以假设着色点有一个属性,我们可以通过调整这个属性来达到显示不同的颜色,即吸收或反射哪些光。

漫反射的计算

到这一步,我们已经知道一个着色点能接收到多少能量L了,再结合颜色原理,我们只需要定义一个系数 ,即可决定该着色点最终的结果,公式如下: 例如 即反射所有的光,那么就是白色, 就等于0,即不反射任何光,即为黑色。

通过不同的值,我们就可以定义出着色点不同的颜色。如下图,光源在左上角,随着的变大,被光照射的部分也越来越亮。同时可以发现相同的情况下,球的左上角亮于中间部分,这也是Lambert的余弦定率的结果,因为法线和光照的夹角大,接收到的能量小,所以暗。

image-20220917151232008

因为漫反射是将均匀的反射出去,所有我们不管在任何方向看,结果都应该是一样的,因此式子也和观察方向 v,没有什么关系。

注:在Blinn-Phong模型中,我们不会考虑摄像机和着色点的距离而产生的能量损失。也就是说不会有因为观察距离远,而导致物体看着变暗的效果。

高光

高光是类似于镜面反射的结果造成的,我们知道镜面反射时,出射角和入射角的角度相同的。如下图,所有的入射光都会被反射到R方向附近上去,因此只有当我们的摄像机方向 v 和R离得很近时,我们才能看到反射光,因为光线很集中所以形成了高光效果。

image-20220917153709723

也就是说,我们需要求出R的值来,才能知道R和v是否接近,然而这个R虽然能求,但是其实并不好求,有不少的计算量。Blinn-Phong模型在这里做了很聪明的一步,它发现我们只需要求出 l 和 v 的半程向量 h,然后把它和法线 n 作比较即可,如下图:

image-20220917153831357

我们可以很容易的看出,如果v正好在出射方向上,那么n和h重叠,否则v离出射方向越远,n和h也会越远。这个半程向量h相当好求,它就是v和l组成的平行四边形的对角线,只需要把v和l相加,然后做个归一化即可。公式如下 然后要判断h和n是否接近,我们只需要知道它们的夹角即可,也就是说把 h 和 n相点乘即可。因此我们可得到如下公式: 和漫反射的类似,属于一个我们定义的系数,通常情况下高光是白色的,因此的值也为白色相对应的值。

我们可以发现在高光的公式中,并没有考虑Lambert’s 余弦定率(即没有 项),在这里它被Blinn-Phong模型给简化掉了,主要关注的是是否能看见高光。

此外在高光的公式中还多了一个指数p,等于对夹角的余弦值做了个幂运算,为什么要这步操作呢?我们先来看看对余弦做幂运算会有什么结果,如下图:

image-20220917154547546

如果没有p指数或者p=1,我们会发现假如夹角45度, 的值约等于0.7,也就是说依旧能看见很明显的亮度,但是我们不希望这种情况出现,因为45度左右我们就不应该能看见高光了。因此我们增加了指数p,从图中可以看出,当P的值越大, 的值就越快接近于0,这样就不会造成当夹角较大时,我们依旧能看见高光的现象了。通常情况下,在Blinn-Phong模型中p的取值会是在100-200之间。

我们来看看实际效果,如下图:

image-20220917154716232

图中是漫反射+高光的效果,小球整体效果就是漫反射的结果,而其中较小的那些小圈就是加上高光后的结果。可以看出越大,小圈越亮,因为反射的光越多。p越大,小圈越小,因为偏移一点角度就会使cos的值为0。

环境光

因为光线可以不断的折射,因此即使没有被光源直接照射到的着色点依旧可能被间接光所照射到,从而产生亮度。但是这一部分的计算非常的复杂,因此在Blinn-Phong模型中,我们假设每个着色点接收到的环境的光永远都是相同的,定为

image-20220917154850279

如图,因为是环境光,因此和光源方向 以及着色点法线n就没有关系,同样的应该不管从什么方向看,结果都应该是一样的,因此和视线v也没有关系。

因此我们就可以得到环境光的公式 与其他公式类似,也会有个系数 ,用来控制能反射多少能量。因此环境光就是一个常量,可以当作代表的就是某一种颜色,也就是说物体上的每个着色点都带有这个颜色,因此整体效果也会比较平滑。

最终效果

好了,在说完上面三个大方面之后,我们把它加起来看得到的最终公式如下: 最终着色效果如下

image-20220917155134283

使用上诉方法进行着色,就是我们所说的Blinn-Phong着色模型。当然了,里面很多都是假设得到的结果,因此Blinn-Phong着色模型得到的结果只是真实环境的一个近似。

着色频率

既然我们前面提到了着色是给每个着色点进行着色,而着色的方法/公式上面已经介绍了其中一种了,那么在实际的着色过程中,着色点到底是什么?

我们先来看看下面这张图:

image-20220917160009413

上图中的三个球,从边缘我们可以发现它们属于相同的几何模型,但是我们可以看出着色出来的结果却是各不相同。

  • 第一个球,我们可以明显看出它是对Mesh的每个面进行着色,也就是说我们的着色点实则是一个个的面。
  • 第二个球,则是把每个面的顶点当做着色点进行着色,那么一个面的不同顶点就可能是不同的颜色,面内某一点的颜色可以使用插值的方法来计算。
  • 第三个球,自然就是把每个像素当做一个着色点进行着色。

这些着色方法的不同,我们称之为着色频率不同,可以看出不同的着色频率会产生不同的效果。

平面着色

平面着色就是对每个三角形面进行着色(shade each triangle),因此每个三角形内部的颜色都会是一样的,效果如下:

image-20220917160205244

关于着色的计算,三角形的法线很好求得,将任意两条边进行叉乘即可。光源方向与距离和摄像机方向自然也是根据每个三角形而计算。可以看出,这种方法得到的效果并不算特别好。

高洛德着色

高洛德着色,又称平滑着色(Smooth Shading),它则是针对顶点的着色(shade each vertex),效果如下:

image-20220917160322500

我们只要求出每个顶点的法线,即可计算出每个顶点的着色,那么顶点的法线应该怎么计算呢?我们知道一个顶点可能连接着多个不同的三角形面(如下图),而每个三角形面都会有它各自对应的法线,那么这个顶点的法线值就是这些面的法线值的平均。

image-20220917160506110

注:更准确的算法是,根据每个三角形的面积求一个权重,面积越大权重越高,然后根据权重来计算顶点的法线。

可以看出这种算法比平面着色要好了不少

冯氏着色(Phong)

冯氏着色则是针对每个像素的着色(Shade each pixel),效果如下:

image-20220917160617152

与之前提到的Blinn-Phong着色模型一样,都是由Phong发明的。

既然每个像素着色,那么自然要计算每个像素的法线,我们知道每个像素会对应到一个三角形的平面,但是它们的法线并不是平面的法线,而是根据三个顶点的法线来进行插值计算,如下图:

image-20220917160737261

中间的这些即是像素的法线,根据两个顶点的法线通过重心坐标的方法计算得到。

可以看出,这种方法得到的着色效果就非常的好了。

image-20220917161205929

可以看出,当模型面较少时,平面着色的效果最差,冯氏着色的效果最好。但是当模型的面越来越多时,三者的效果也基本相似。