光照模型
当光照射到物体表面时,物体会对光会发生反射、透射、吸收、衍射、折射和干涉。
其中被物体吸收的部分转化为了热,而其他形式的光进入了人的眼睛,成为了我们看到的样子。
为了模拟这些现象,人们建立了一些数学模型,用来代替现实生活中复杂的物理模型,这些数学模型就称为明暗效应模型或者光照模型。
这里只涉及一些传统的经验光照模型(Lambert模型、Half Lambert模型、Phong模型、Blinn-Phong模型)。
无光照模型
假设渲染一个物体,但是不应用任何光照模型,那么事实上,就只是把模型的贴图显示出来而已。
1 2 3 4 5 6 7 8 9 10 11 12 13
| varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
uniform sampler2D uTexture; varying vec2 vUv; void main() { gl_FragColor = texture2D( uTexture, vUv ); }
|
不应用光照模型虽然也能看到物体,但是立体感较差,并且不会有光线的明暗信息。
定义一些方向
使用光照模型之前首先需要定义一些东西。
我们考虑的着色结果,都是考虑在某一个点上应该是什么样的结果,而这个点通常称为着色点(shading point)。
每一个shading point都应该在物体表面上。虽然,物体表面可能是一个曲面,但是仍然可以认为,它是由很多小的平面构成的。
在一个平面内,可以定义:
- 平面的法线方向n:垂直平面,并由内指向外。
- 观测方向v:shading point与相机位置的连线方向。
- 光源方向l:shading point与光源位置的连线方向。

Lambert模型
Lambert模型是一种经验模型,主要用来简单的模拟粗糙物体表面的光照现象。
从日常生活中可以发现,一个物体面向光源的地方更亮,而背向光源的地方更暗。
而在一个shading point中判断其是否面向光源,其实可以通过判断该shading point的法向量n是否与光源方向l足够接近,也就是他们之间的夹角是否足够小。夹角越小,光线越强,夹角越大,光线越弱。
图形学基础—向量 _ 向量的点乘在图形学上的作用中有提到,通过向量的点乘就可以判断两个向量之间是否足够接近。
Lambert模型分为两个部分:环境光、漫反射。
ILambert=ambient+diffuse
环境光
环境光和其他光照模型一样
ambient=Ka∗ambientColor
- Ka:材质的环境光反射系数
- ambientColor:是环境光强度和颜色数值,通常是vec3类型
漫反射
漫反射项也和其他模型一样
diffuse=Kl∗lightColor∗max(0.0,dot(N,L))
- Kl:材质的灯光反射系数(如果使用贴图,那就用贴图颜色作为漫反射系数)
- lightColor:是灯光强度和颜色数值,通常是vec3类型
- N:法向量方向
- L:光线方向
最终颜色
最终Lambert模型的颜色为:
finalColor=ambient+diffuse
- ambient:环境光
- diffuse: 漫反射
glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| uniform vec4 lightPosition; varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition;
void main() { vUv = uv; vNormal = normalize( normalMatrix * normal ); vPosition = modelViewMatrix * vec4(position, 1.0); vlightPosition = modelViewMatrix * lightPosition; gl_Position = projectionMatrix * vPosition; }
uniform sampler2D uTexture; uniform vec3 ambientColor; uniform vec3 lightColor; uniform float K_a;
varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition;
void main() { vec3 vL = normalize(vlightPosition.xyz - vPosition.xyz); vec3 ambient = ambientColor * K_a; vec3 K_l = texture2D( uTexture, vUv ).rgb; vec3 diffuse = K_l * lightColor * max(0.0, dot(vNormal, vL)); gl_FragColor = vec4(ambient + diffuse,1.0); }
|
着色效果
可以看到加了Lambert模型之后,渲染效果比之前好多了,增加了立体感。
但是还有一个弊端:在光源照不到的地方,Lambert模型表现的效果是全黑的。但事实上,我们日常所处的环境中很多时候会有环境光,不会导致全黑的效果。
而另一个简单的经验模型就弥补了这一弊端。
Half Lambert模型
由于向量的点乘结果在-1到1之间。而在光照模型中,我们只会使用0到1之间的值,小于0的值认为该光源被遮挡对着色没有影响。
也正是因为我们使用了0到1之间的值,所以导致在光源无法照射到的地方,着色结果是全黑的。
而Half Lambert模型想法很简单,就是把0到1之间的值变成0.5到1。
有趣的是Half Lambert光照模型是Valve公司在制作“半条命(Half-Life)”的时候发明的。而这个模型的想法正好也与Half有关。

Half Lambert模型的漫反射
Half Lambert模型与Lambert模型的区别在漫反射公式,Half Lambert模型的漫反射公式:
diffuse=Kl∗lightColor∗(max(0.0,dot(N,L))∗0.5+0.5)
- Kl:材质的灯光反射系数(如果使用贴图,那就用贴图颜色作为漫反射系数)
- lightColor:是灯光强度和颜色数值,通常是vec3类型
- N:法向量方向
- L:光线方向
glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| uniform vec4 lightPosition; varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition;
void main() { vUv = uv; vNormal = normalize( normalMatrix * normal ); vPosition = modelViewMatrix * vec4(position, 1.0); vlightPosition = modelViewMatrix * lightPosition; gl_Position = projectionMatrix * vPosition; }
uniform sampler2D uTexture; uniform vec3 ambientColor; uniform vec3 lightColor; uniform float K_a;
varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition;
void main() { vec3 vL = normalize(vlightPosition.xyz - vPosition.xyz); vec3 ambient = ambientColor * K_a; vec3 K_l = texture2D( uTexture, vUv ).rgb; vec3 diffuse = K_l * lightColor * (max(0.0, dot(vNormal, vL)) * 0.5 + 0.5); gl_FragColor = vec4(ambient + diffuse,1.0); }
|
着色效果
可以看到使用了Half Lambert模型之后,渲染效果相比于使用Lambert模型,在暗部变得更亮了,更符合现实场景。
Phong模型
在日常生活中,我们可以发现,在一些光滑的物体表面,除了会有类似Lambert模型那样的明暗关系,还会有一些“布灵布灵”的高光。
并且这些高光的位置还会随着观察的方向变化而变化。
而Phong模型就是在Lambert模型下增加了高光项。
因此Lambert模型通常用来描述粗糙的物体表面,而Phong模型通常用来描述光滑的物体表面。
Phong模型分为三个部分:环境光、漫反射、高光。
Iphong=ambient+diffuse+specColor
环境光
环境光和其他光照模型一样
ambient=Ka∗ambientColor
- Ka:材质的环境光反射系数
- ambientColor:是环境光强度和颜色数值,通常是vec3类型
漫反射
漫反射项也和其他模型一样(这里为了使得暗部显示正常,所以用了Half Lambert模型)
diffuse=Kl∗lightColor∗(max(0.0,dot(N,L))∗0.5+0.5)
- Kl:材质的灯光反射系数(如果使用贴图,那就用贴图颜色作为漫反射系数)
- lightColor:是灯光强度和颜色数值,通常是vec3类型
- N:法向量方向
- L:光线方向
高光
要计算高光,首先需要知道高光产生的原因。
高光产生的原因
高光是由于物体表面较为光滑,对光的反射较强。
当光线照射到物体表面之后会被反射出来。
如果此时我们看向的方向正好与反射的光线方向很接近,就会看到一个很明显的光斑,这个光斑就是我们所说的高光。

如图所示,光线沿着-l方向照射到物体表面,并且沿着R方向被反射出来,如果此时R方向与V方向足够接近,那么就会看到一个高光。
高光的计算
高光是由于反射光的方向与观测方向足够接近,那么我们就需要先计算反射光方向。

如上图所示,L为光线方向,R为反射方向,n为法线方向。
由于向量平移之后仍然可以表示本身,因此可以将R平移,使得R的起点与L的终点重合。
假设B=L+R,则向量B一定在n方向上。
并且假设L在n方向上的投影为b,那么b=(L⋅n)∗n
由于L与R的长度相同,因此向量L、R、B组成的三角形是等腰三角形。
从而B=2∗b,也就是2∗b=L+R=2∗(L⋅n)∗n。
所以反射方向R=2∗(L⋅n)∗n−L
知道反射方向之后,就可以写出高光的表达式:
specColor=Ks∗highlightColor∗max(0.0,dot(v,R))
- Ks:材质的高光反射系数
- highlightColor:是高光强度和颜色数值,通常是vec3类型
- v:观测方向
- R:光线反射方向
渲染结果如下:
从渲染结果中可以看到,确实相比于Half Lambert模型增加了一些高光项,但是高光范围很大,不太像我们在现实生活中看见的小的光斑。
因此我们需要用指数函数缩小光斑的范围,修改之后的高光表达式为:
specColor=Ks∗highlightColor∗pow(max(0.0,dot(v,R)),smoothness)
- smoothness:高光的平滑度
设置平滑度smoothness为40之后,可以看到光斑的范围缩小了很多,更符合现实生活中看到的样子。

glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| uniform vec4 lightPosition; uniform vec4 cPosition; varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition; varying vec4 vCPosition;
void main() { vUv = uv; vNormal = normalize( normalMatrix * normal ); vPosition = modelViewMatrix * vec4(position, 1.0); vlightPosition = modelViewMatrix * lightPosition; vCPosition = modelViewMatrix * cPosition; gl_Position = projectionMatrix * vPosition; }
uniform sampler2D uTexture; uniform vec3 ambientColor; uniform vec3 lightColor; uniform vec3 highlightColor; uniform float K_a; uniform float K_s; uniform float smoothness;
varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition; varying vec4 vCPosition;
void main() { vec3 ambient = ambientColor * K_a; vec3 vL = normalize(vlightPosition.xyz - vPosition.xyz); vec3 K_l = texture2D( uTexture, vUv ).rgb; vec3 diffuse = K_l * lightColor * (max(0.0, dot(vNormal, vL)) * 0.5 + 0.5); vec3 v = normalize(vCPosition.xyz - vPosition.xyz); vec3 R = 2.0 * dot(vNormal, vL) * vNormal - vL; vec3 specColor = K_s * highlightColor * pow(max(0.0, dot(v, R)), smoothness); gl_FragColor = vec4(ambient + diffuse + specColor, 1.0); }
|
Phong模型着色效果
Blinn-Phong模型
在前面有说到高光是因为反射的光线方向与观测方向接近,因此计算高光之前我们需要计算反射的方向。
反射的方向计算比较复杂,比较耗时,而Blinn-Phong模型则解决了这一问题。
高光的计算
人们巧妙地发现了其实只需要计算光线方向l 与观测方向v 的半程向量h 是否与法向量n 足够接近就可以了。

而半程向量的计算非常简单,只需要将两个向量相加,再单位化即可。
h=normalize(l+v)
因此Blinn-Phong光照模型的高光项就可以表示为:
specColor=Ks∗highlightColor∗pow(max(0.0,dot(n,h)),smoothness)
Blinn-Phong光照模型极大的简化了Phong模型中的高光计算,并且能够达到与Phong模型类似的效果。
glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| uniform vec4 lightPosition; uniform vec4 cPosition; varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition; varying vec4 vCPosition;
void main() { vUv = uv; vNormal = normalize( normalMatrix * normal ); vPosition = modelViewMatrix * vec4(position, 1.0); vlightPosition = modelViewMatrix * lightPosition; vCPosition = modelViewMatrix * cPosition; gl_Position = projectionMatrix * vPosition; }
uniform sampler2D uTexture; uniform vec3 ambientColor; uniform vec3 lightColor; uniform vec3 highlightColor; uniform float K_a; uniform float K_s; uniform float smoothness;
varying vec2 vUv; varying vec3 vNormal; varying vec4 vPosition; varying vec4 vlightPosition; varying vec4 vCPosition;
void main() { vec3 ambient = ambientColor * K_a; vec3 vL = normalize(vlightPosition.xyz - vPosition.xyz); vec3 K_l = texture2D( uTexture, vUv ).rgb; vec3 diffuse = K_l * lightColor * (max(0.0, dot(vNormal, vL)) * 0.5 + 0.5); vec3 v = normalize(vCPosition.xyz - vPosition.xyz); vec3 h = normalize(v + vL); vec3 specColor = K_s * highlightColor * pow(max(0.0, dot(vNormal, h)), smoothness); gl_FragColor = vec4(ambient + diffuse + specColor, 1.0); }
|
Blinn-Phong模型着色效果