冯氏光照模型
现实生活中的光照效果是得长复杂的,要精确模拟这种效果是不现实的,所以需要一种能够近似现实光照效果的简化模型。比较著名的是冯氏光照模型
(Phong Lighting Model)。
在冯氏光照模型中,生活中的光照有三种类型:环境光(Ambient)、漫反射(Diffuse)和镜面高光(Specular)
1
| 总光照效果 = 环境光 + 漫反射 + 镜面高光
|
环境光
某些光从光源发出后,被多个物体(墙壁、镜子等)反射 ,最终照射到物体上。对于场景中这种经过反射多次以至于无法确定来自某个方向的光,被称为环境光。在冯氏模型中,环境光从各个角度照射物体,其强度都是一致。
通常情况我们使用一个较小的光线因子乘以光源颜色
来模拟。
漫反射
漫反射是为了模拟平行光源、点光源对物体的方向性影响。
我们知道,当一束光线照射到物体表面时,光线的入射角越小,该表面的亮度就越大,看上去也就越亮。反之,该表面的亮度就越小,看上去越暗。
入射角的表示:
要定义入射角,需要先定义一个法向量
,即垂直于物体表面,并且朝向平面外部的向量。
1
| 漫反射光照 = 入射光颜色 * 表面基颜色 * 入射角的余弦值
|
几何体在旋转、缩放时,每个表面的法向量也会随之变化。
- 平移变换不会改变法向量,因为平移不会改变物体的方向。
- 旋转变换会改变法向量,因为旋转改变了物体的方向。
- 拉伸的情况比较复杂,如果在所有轴上的缩放比例都是一样的,那么法向量不会发生变化
如何计算变换之后的法向量呢?
只要将变换之前的法向量乘以模型矩阵的逆转置矩阵(inverse transpose matrix)即可 。具体操作就是:对模型矩阵先求逆,再转置。
模型矩阵即:对顶点进行变换(缩放、位移、旋转)的矩阵 。
平行光源
在现实中平行光漫反射可以模拟遥远的光源,比如太阳光,由于太阳距离地球过于遥远,所以太阳光照射在物体各个点的方向可以看做是平行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| attribute vec3 a_Color; attribute vec4 a_Normal;
uniform mat4 u_NormalMatrix; uniform vec3 u_LightDirection;
varying vec4 v_Color;
void main(){
.......
vec4 color = vec4(a_Color.xyz * 1.0, 1.0); vec4 normal = u_NormalMatrix * a_Normal; float nDotL = max(dot(u_LightDirection, normalize(normal.xyz)), 0.0);
v_Color = vec4(color.xyz * nDotL, 1.0); }
|
点光源
如果光源距离物体比较近,那就不能把光源看作平行的了,照在物体不同点时,入射角也会不一样,在一个平面上照强度也会有差别,距离光源近的部分比较亮,距离光源远的部分比较暗。
对比平行光源,点光源光的方向不再是恒定不变的,而要根据每个顶点的位置逐一计算。着色器需要知道点光源光自身的所在位置,而不是光的方向。
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 46 47 48
| <script id="vertexShader" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 a_Position; attribute vec3 a_Color; attribute vec4 a_Normal;
uniform mat4 u_MvpMatrix; uniform mat4 u_NormalMatrix; uniform mat4 u_ModelMatrix; uniform vec3 u_LightPosition; uniform vec3 u_LightColor; uniform vec3 u_AmbientLight;
varying vec4 v_Color;
void main(){ gl_Position = u_MvpMatrix * vec4(a_Position,1.0); vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal)); vec4 worldPositon = u_ModelMatrix * vec4 (a_Position ,1.0);
vec3 lightDirection = normalize(u_LightPosition - vec3(worldPositon.xyz)) ; float nDotL = max(dot(lightDirection , normalize(normal.xyz)), 0.0);
vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;
vec3 ambient = u_AmbientLight * a_Color.rgb;
v_Color = vec4(diffuse + ambient, 1);
} </script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_Color;
void main(){ gl_FragColor = v_Color; } </script>
|
上面的效果也不完美,可以看到有很明显的明暗分界线。这是因为,上面代码只计算了6个点的颜色,其他颜色都是通过插值生成的。
下面可以试一下逐片加载的方式,处理点光源。也就是把计算颜色的逻辑放到片元着色器上面。
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 46 47 48 49 50 51 52 53 54
| <script id="vertexShader" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 a_Position; attribute vec3 a_Color; attribute vec4 a_Normal;
uniform mat4 u_MvpMatrix; uniform mat4 u_NormalMatrix; uniform mat4 u_ModelMatrix;
varying vec4 v_Color; varying vec3 v_Normal; varying vec3 v_Position;
void main(){ gl_Position = u_MvpMatrix * vec4(a_Position,1.0); v_Color = vec4(a_Color , 1.0);
v_Position = vec3(u_ModelMatrix * vec4 (a_Position ,1.0)); v_Normal = normalize(vec3(u_NormalMatrix * a_Normal)); } </script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec3 u_LightPosition; uniform vec3 u_LightColor; uniform vec3 u_AmbientLight;
varying vec4 v_Color; varying vec3 v_Normal; varying vec3 v_Position;
void main(){ vec3 normal = normalize(v_Normal); vec3 lightDirection = normalize(u_LightPosition - v_Position); float nDotL = max(dot(lightDirection, normal), 0.0); vec3 diffuse = u_LightColor * v_Color.rgb * nDotL; vec3 ambient = u_AmbientLight * v_Color.rgb; gl_FragColor = vec4(diffuse + ambient, v_Color.a); } </script>
|
这时候再看,过渡就自然很多了。