计算机图形学中的光照
现实生活中,不同的物体,因为材质的不同,在光照中的表现也不一致。看看下面这个渲染的咖啡机,效果还是栩栩如生的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/2119fdd611dc49b9b8f15ca3cfc0f6a4.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATFblsI_njKrnsr4=,size_20,color_FFFFFF,t_70,g_se,x_16)
(一)Blinn - Phong着色模型
现在来看Blinn - Phong着色模型
上述的图片中光源在右上方,光源照亮了所有的茶杯,可以看到茶杯上有一些颜色不同的地方具体的分析如下
- 可以看到茶杯上出现高光(镜面反射)(Specular highlights)
- 茶杯表面除了高光外,其余颜色变化并不剧烈的地方,我们称之为漫反射光(diffuse reflection)
- 光源在右上,在茶杯的背面应该看不到这个光源,那么茶杯的背面应该是黑色。但是我们看到了这个茶杯的背面并不是黑色,也就是说有一些的光从茶杯的背面反射到了我们的眼里,那么这个点一定是接受到了光。但是这个点接受到的并不是直接光照,而是间接光照。比如说光找到墙上,墙面发生了一个漫反射将光反射到了桌面上,光再经过桌面的反射就能到茶杯的背面。假如说任何一个点都能够接收到环境光(Ambient lighting)
计算某一点的光照
计算一个点的光照,需要的的输入有:
- 观察者的方向向量 ( V i e w e r (Viewer (Viewer d i r e c t i o n , v ) direction , v) direction,v)
- 着色点所在表面的法向量 ( S u r f a c e (Surface (Surface n o r m a l , n ) normal , n) normal,n)
- 光线照射的方向向量 ( L i g h t (Light (Light d i r e c t i o n , I ) direction , I) direction,I)
- 着色点表面的一些参数
(
颜
色
,
材
质
,
反
光
度
等
)
(颜色,材质,反光度等)
(颜色,材质,反光度等)
具体的参数表示如图所示:
1. 漫反射(Diffuse Reflection)
光照射到物体表面时,会均匀的向四面八方进行反射,我们称这种反射为漫反射。漫反射与观察者的位置没有关系,所以观察者不管从什么角度观察,表面的颜色都是一样的。
1.1物体接受光线能量的方式
在现实中,将一个物体放置于一个光源下,若是物体摆放的角度不同,物体的明暗也会不一样。这是什么原因造成的呢?
通过观测可以发现,左边第一幅图,图中有六根光线,每一根光线代表着一股能量,如果物体表面和光线垂直的画,那么物体可以接收到所有光线的能量,若是物体表面旋转了一定的角度,物体表面接受的光线变少,所以物体的表面就暗一些。
所以在图形学中,一个着色点附近的单位面积接受到的光线的能量和光线的入射方向有关。根据
L
a
m
b
e
r
t
’
s
c
o
s
i
n
e
l
a
w
(
兰
伯
特
余
弦
定
理
)
Lambert’s cosine law(兰伯特余弦定理)
Lambert’scosinelaw(兰伯特余弦定理)计算从不同角度接受到的光线能量根据。这个定理是说,接收到的光线的能量与cos(光线的方向,着色点的法向量)值成正比。当光线垂直于物体表面时,物体表面所能够接收到的光线的能量最大,而当光线垂直于物体的表面的法向量时,物体表面就不能接收到光线的能量,表现出来变暗。
1.2光衰减
设想一下我们有一个如下图所示的点光源,朝四面八方发射光能量
按照图中所示,我们假设在半径为1的这个球壳上所蕴含的能量为
I
I
I,在这个球壳上的每一点的光的强度是
I
I
I,那么在半径为
r
r
r的球壳上,在这个球壳上的光线的强度就应该是
I
/
(
r
∗
r
)
I/(r*r)
I/(r∗r)。这就解释了为什么距离越远,光照的强度越弱。
1.3漫反射的强度计算
给定一个点光源,光线的方向向量I,着色点表面的法向量n,观察者的方向向量v,光线的入射角度
θ
θ
θ,漫反射的光强计算如下:
漫反射光强计算公式:
L
d
=
k
d
(
I
/
r
2
)
max
(
0
,
n
⋅
l
)
L_{d}=k_{d}\left(I / r^{2}\right) \max (0, \mathbf{n} \cdot \mathbf{l})
Ld=kd(I/r2)max(0,n⋅l)
式中,kd是一个系数,代表的是一个物体本身的颜色,或者是材质。不同的材质和光线作用的结果不同,所以还要再乘上这个系数。
2.镜面反射(Specular reflection)
当物体的表面很光滑的时候,在一定的角度范围内观察这个平滑的表面表面时,能够看到一个高光的效果。如在用光源照射金属时,在一定的角度内可以看到一个有一定大小的光点,但你偏离一定的角度之后,就不能再看到这个高光效果。在漫反射的部分已经讲过,漫反射的着色与观察方向无关,而镜面反射不同,镜面反射的效果是与观察方向相关的。当观察方向和光的反射方向越近的时候,观察到的镜面反射效果越强镜面反射示意图如图所示:
那么问题来了,怎么衡量上图中反射光线与观察方向是否在高光范围内呢(是否很接近产生高光)
- 方法一:(冯氏光照模型的方法)可以用反射光的方向向量和观察方向的方向向量做点乘,得到其夹角的余弦值,点乘的结果越大,则反射光和观察方向就越近。
- 方法二:(Blinn - Phong光照模型的方法)对冯氏模型的一个改进,不是计算反射光和观察方向的远近,而是计算半程向量h和该平面的法向量n的远近。因为反射光和观察方向越近,半程向量和法向量也就越近,且计算反射光的计算量比较大,而半程向量h只需要将v和l相加,再标准化即可得到,计算简单。方法二如图所示:
- 半程向量计算式子(并且归一化)如下:
h ( 半 程 向 量 ) = bisector ( v , l ) = v + l ∥ v + l ∥ \mathbf{h(半程向量)}=\operatorname{bisector}(\mathbf{v}, \mathbf{l}) =\frac{\mathbf{v}+\mathbf{l}}{\|\mathbf{v}+\mathbf{l}\|} h(半程向量)=bisector(v,l)=∥v+l∥v+l
最终计算某一点的高光强度的计算方法如下:
L
s
=
k
s
(
I
/
r
2
)
max
(
0
,
cos
α
)
p
=
k
s
(
I
/
r
2
)
max
(
0
,
n
⋅
h
)
p
\begin{aligned} L_{s} &=k_{s}\left(I / r^{2}\right) \max (0, \cos \alpha)^{p} \\ &=k_{s}\left(I / r^{2}\right) \max (0, \mathbf{n} \cdot \mathbf{h})^{p} \end{aligned}
Ls=ks(I/r2)max(0,cosα)p=ks(I/r2)max(0,n⋅h)p
参数解释:
- ks:反光系数,不同的材质对光的反射程度不同,如金属和木头对光的反射效果就不同
- 指数p,先看图:
可以看到指数越大,cos不为0的范围就越小。应该这样理解,如果p为1,那么在(0 - 90°)的这样一个范围内都可以看到一个高光效果,但现实生活中观察的偏离角度较大的时候就不能看到高光了,而随着p的增大,能看到高光的角度就会缩小,比如p = 1时,在45°就能看到高光效果,而p = 64时,在45°就看不到高光效果了。也就是说p越小,对高光的容忍度越高,但是我们平时看到的高光都是集中在一点并且是在一个比较小的范围内,所以用较高的指数p将高光限制在一个很小的范围内。
p取值的高光效果如图所示:
ks和p对高光效果的影响如图所示:
可以看到材质反光系数ks越大,高光范围越大;p越大,高光范围越小。
3.环境光 (Ambient Lighting)
环境光是由许多光线经过多次漫反射最终打在某一点上,实际上环境光比较复杂,一般做法将环境光设置为一个常数,假设任何一点接受到的都是一个相同的环境光。环境光不用考虑光的方向,与观察的方向也无关。
4.Blinn-Phong模型的最终效果
将三种光照效果叠加在一起,得到最终的光照效果如图所示:
L
=
L
a
+
L
d
+
L
s
=
k
a
I
a
+
k
d
(
I
/
r
2
)
max
(
0
,
n
⋅
l
)
+
k
s
(
I
/
r
2
)
max
(
0
,
n
⋅
h
)
p
\begin{aligned} L &=L_{a}+L_{d}+L_{s} \\ &=k_{a} I_{a}+k_{d}\left(I / r^{2}\right) \max (0, \mathbf{n} \cdot \mathbf{l})+k_{s}\left(I / r^{2}\right) \max (0, \mathbf{n} \cdot \mathbf{h})^{p} \end{aligned}
L=La+Ld+Ls=kaIa+kd(I/r2)max(0,n⋅l)+ks(I/r2)max(0,n⋅h)p
(二)着色频率(Shading Frequencies)
下图所示为不同的着色效果
左图能够明显的看到一个个四边形 ,中间的球的效果就平滑一些,右边的图更光滑。
下面一一介绍三种不同的着色方式:
1.平面着色(Flat Shading)
这种着色方式可以明显的看出球表面上的三角形,感觉到球上面有许多棱角,在球上的着色并不平滑。(逐面片渲染)
这种着色方法,是在一个三角形上取一个点进行着色,将这个点作为该面的着色(一个面上着色一次),在三角形的内部并不会发生颜色的变化,所以可以明显的看到球上的每个三角形。
2.高洛德着色(Gouraud Shaing)
这种着色方式明显比上面的着色方法渲染的球体要平滑许多。(逐顶点渲染)
这种着色方法,是在每个三角形的顶点上进行一次着色,然后三角形内部的颜色为三个顶点的插值,这样三角形内部的颜色就会有一个渐变的效果,也能够更加平滑。
2.1 定义逐顶点的法线(Defining Per-Vertex Normal Vectors)
每一个物体都是由许多三角形相连无缝贴合组成的,所以每一个顶点就会和许多不同的三角形有所关联,即一个顶点就是多个三角形的顶点,所以我们就认为这个顶点的法线就是周围相邻几个面的法线平均。并且将每个三角形的面积作为权值,对每个法向量进行加权平均,并且在求平均时要对向量进行标准化。
3.冯着色(Phong shading)
这种着色方式明显比上两种着色方法渲染的球体更光滑。(逐像素渲染)
在每一个像素上都可以求得一个独特的法向量,然后就可以在每一个像素上都进行一次着色,这样就可以得到一个相对比较好的结果。这种方法是在每一个像素上都进行一次着色。
从上面的图可以看出,并不能简单的说哪种着色效果更好,着色的频率取决于面、点或者像素出现的频率,如果面的频率出现很高的话,那么用flat shading也是可以达到一个平滑的效果,如上图中最后一行。如果使用flat shading就能达到一个不错的效果,那么如果使用Phong shading的话就会增加额外的计算量,效率上就会降低 。
3.1 定义逐像素的法线(Defining Per-Pixel Normal Vectors)
比如说在三角形的内部已经知道每一个顶点的法线,如何得到内部一个平滑过渡的法线?
给出顶点的法线,如何插值出中间的法线?这里就需要用到重心坐标
3.2 插值 - 重心坐标 (Barycentric Coordinates)
为什么需要要插值?
- 能够获得三角形三个固定顶点的属性,但是不知道三角形内部的属性
- 希望三角形内部属性能有一个平滑的过度效果
可以插值的属性有哪些?
- 纹理坐标、法向量、颜色等等
假设有一点
(
x
,
y
)
(x,y)
(x,y)位于三角形内部,可以表示为三个顶点A、B、C坐标的线性组合。如图
α
,
β
,
γ
α,β,γ
α,β,γ分别为点A、B、C的系数,三个点线性组合能够得到三角形上任一点的坐标。如果
α
,
β
,
γ
α,β,γ
α,β,γ的和为1,那么点
(
x
,
y
)
(x,y)
(x,y)就在三角形所在的平面上,如果和不为1,那么点
(
x
,
y
)
(x,y)
(x,y)就不在三角形所在的平面内。如果三个数和为1,并且都非负,则这个点在三角形内部。
看看重心坐标的例子:
任意一点的的重心坐标可以通过求面积比计算出来,下面是一种简化的计算方式:
α
=
−
(
x
−
x
B
)
(
y
C
−
y
B
)
+
(
y
−
y
B
)
(
x
C
−
x
B
)
−
(
x
A
−
x
B
)
(
y
C
−
y
B
)
+
(
y
A
−
y
B
)
(
x
C
−
x
B
)
β
=
−
(
x
−
x
C
)
(
y
A
−
y
C
)
+
(
y
−
y
C
)
(
x
A
−
x
C
)
−
(
x
B
−
x
C
)
(
y
A
−
y
C
)
+
(
y
B
−
y
C
)
(
x
A
−
x
C
)
γ
=
1
−
α
−
β
\begin{aligned} \alpha &=\frac{-\left(x-x_{B}\right)\left(y_{C}-y_{B}\right)+\left(y-y_{B}\right)\left(x_{C}-x_{B}\right)}{-\left(x_{A}-x_{B}\right)\left(y_{C}-y_{B}\right)+\left(y_{A}-y_{B}\right)\left(x_{C}-x_{B}\right)} \\ \beta &=\frac{-\left(x-x_{C}\right)\left(y_{A}-y_{C}\right)+\left(y-y_{C}\right)\left(x_{A}-x_{C}\right)}{-\left(x_{B}-x_{C}\right)\left(y_{A}-y_{C}\right)+\left(y_{B}-y_{C}\right)\left(x_{A}-x_{C}\right)} \\ \gamma &=1-\alpha-\beta \end{aligned}
αβγ=−(xA−xB)(yC−yB)+(yA−yB)(xC−xB)−(x−xB)(yC−yB)+(y−yB)(xC−xB)=−(xB−xC)(yA−yC)+(yB−yC)(xA−xC)−(x−xC)(yA−yC)+(y−yC)(xA−xC)=1−α−β
使用重心坐标插值得到的三角形如图所示
对三角形进行操作时,都是对三角形的三个顶点进行操作,所以三个顶点的属性都是知道的。要对三角形内部进行插值,就运用到了重心坐标,前面已经说过,三角形内部的任意一点都可以用三个顶点线性表述出来,三个顶点的系数就是插值的权重的大小,然后使用同样的系数,对三个顶点的属性进行线性组合,得到的结果就是三角形内部插值的值。假设三角形内部的某一点V的重心坐标是
(
α
,
β
,
γ
)
(α,β,γ)
(α,β,γ),则对该点属性进行插值就是:
V
=
α
V
A
+
β
V
B
+
γ
V
C
V=\alpha V_{A}+\beta V_{B}+\gamma V_{C}
V=αVA+βVB+γVC
重心坐标的缺陷
注意:重心坐标并不是永久不变的,在经过投影后,重心坐标可能会发生改变。因为在投影之后,原来的三角形会发生变形,点的相对位置就会发生改变,投影之后计算出的重心坐标和投影前的重心坐标就并不相同。
所以要插值三维空间的属性时,要先在三维空间中算出重心坐标,然后再用在三维空间中算出重心坐标进行插值,而不能使用投影之后的坐标进行插值。三角形投影到屏幕上,覆盖很多像素,像素都有中心,可以判断出像素的中心在投影到屏幕上的三角形的某一位置上,在投影的三角形里对三个顶点的深度做插值,这种算法是不对的,因为三角形在投影到屏幕的过程中会经过一个深度测试的过程,其中深度值较大的顶点会被丢弃,所以如果用投影后的三角形的重心坐标计算插值,可能就会少某些顶点或者说对应的顶点的属性发生改变(因为没有通过深度测试的顶点被丢弃了,在这个位置上时其他不同属性的顶点)。而正确的做法是找到这个这个像素对应的三维空间中的三角形,在三维空间中将深度插值好,然后再放回来。