如何在GL系统中实现光照
让我们先看一张图:
简单的光照有3种类:
他们是 平行光
,点光
,环境光
1.平行光:
顾名思义,平行光是平行的光,是一个距离我们无限远的点发出的光,比如说太阳. 我们可以用一个 方向(vec3) 和一个颜色(vec3) 来表示.(光没有alpha分量)
2.点光:
类似灯泡,是一个点发出的光,它可以照亮一片区域, 而且垂直在他下方的点会比其他点更亮一些. 我们可以用 一个光源位置(vec3),和一个颜色(vec3)来表示.
3.环境光:
类似于自然中 被粗糙表面反弹回来的光,他无处不在,(一般都比较弱) 我们一般用一个颜色就可以.
物体被光线照射
我们以前做的例子都是无光照的,没有光照的物体会丧失立体感,显得不太自然.
比如太阳光照在一张红纸上,人可以看到是红色 为什么呢? 原因是太阳光照在物体上,被物体反射的光射入眼睛,所以我们看到了颜色.
我们来分析一下:
太阳光 白光 vec(1,1,1)
红纸 vec(1,0,0)
怎么得到 红色的反射光呢?
很简单,相乘就可以 vec(1,1,1) × vec(1,0,0) = vec(1,0,0); 既是我们看到的红色.
照射角
照射角指的是平面法线
(垂直于平面)与光照的角度.
日常生活中,正午的阳光会比黄昏的更刺眼,是因为太阳光与地球直射,所以说明了一个问题就是角度决定光照强度
.
90°最强 0°最弱.
照射角度怎么求得:
根据矢量点乘法则:
向量:a(x1,y1,z1),b(x2,y2,z2);
我们都知道 a·b=|a||b|·cosθ 也就是说 如果 |a| 和 |b| 都是 1的话 那么 a·b = cosθ 也就是说 a·b= x1x2 + y1y2 + z1z2 = cosθ;
也就是说 归一化的法向量 · 归一化的光照方向向量 = cosθ
这里涉及到一个 归一化
的概念,所谓归一化就是将向量的长度换算成单位长度1, 也就是说 vec(1,1,1) 与 vec(100,100,100) 经过归一化后的值是一样的.
归一化的光照方向向量:
glsl提供了内置函数 normalize
使用如下 normalize(vec3(xxx));
归一化的法向量:
法向量是垂直于平面的向量,但是垂直于平面的向量有2个方向,这里我们遵循 右手法则.
平面的法向量有无数条,因为法向量只规定了与平面垂直,但过平面内一点的法线只有2条, 我们只使用右手法则下的那一条.
在webgl中法向量一般是伴随模型数据 一起传入 shader中..
比如下面 定义了一个带法向量的 CUBE:
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
// Coordinates 定义面
var vertices = new Float32Array([
2.0, 2.0, 2.0, -2.0, 2.0, 2.0, -2.0,-2.0, 2.0, 2.0,-2.0, 2.0, // v0-v1-v2-v3 front
2.0, 2.0, 2.0, 2.0,-2.0, 2.0, 2.0,-2.0,-2.0, 2.0, 2.0,-2.0, // v0-v3-v4-v5 right
2.0, 2.0, 2.0, 2.0, 2.0,-2.0, -2.0, 2.0,-2.0, -2.0, 2.0, 2.0, // v0-v5-v6-v1 up
-2.0, 2.0, 2.0, -2.0, 2.0,-2.0, -2.0,-2.0,-2.0, -2.0,-2.0, 2.0, // v1-v6-v7-v2 left
-2.0,-2.0,-2.0, 2.0,-2.0,-2.0, 2.0,-2.0, 2.0, -2.0,-2.0, 2.0, // v7-v4-v3-v2 down
2.0,-2.0,-2.0, -2.0,-2.0,-2.0, -2.0, 2.0,-2.0, 2.0, 2.0,-2.0 // v4-v7-v6-v5 back
]);
// Colors 定义面上的颜色
var colors = new Float32Array([
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 // v4-v7-v6-v5 back
]);
// Normal 定义法线
var normals = new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);
// Indices of the vertices 定义索引数据
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
为立方体加上平行光
attribute vec4 a_Position; //顶点
attribute vec4 a_Color; //颜色
attribute vec4 a_Normal; //法向量
uniform mat4 u_MvpMatrix; //变换矩阵
uniform vec3 u_LightColor; //光色
uniform vec3 u_LightDirection; //光方向
varying vec4 v_Color; //计算后的颜色
void main() {
gl_Position = u_MvpMatrix * a_Position ;
vec3 normal = normalize(a_Normal.xyz);//归一化法向量
float nDotL = max(dot(u_LightDirection, normal), 0.0); //计算cos
vec3 diffuse = u_LightColor * a_Color.rgb * nDotL; //颜色 * 光 * cos
v_Color = vec4(diffuse, a_Color.a); //最后的颜色
}
感觉更有立体感了,但右侧为什么是黑的?. 原因是光源在左侧,右侧面被挡住了.
但即便是被挡住了,那也不应该是纯黑色. 应该只是比迎光面暗一些才对.
这时候就需要另一种光, --- 环境光
环境光
没有方向,可以简单理解他是来自四面八方的平行光,没有一个面能逃得过 环境光
.
当环境光
与 平行光
相加,效果会更自然..
为立方体加上环境光
uniform vec3 u_AmbientLight;
...
v_Color = vec4(diffuse + ambient, a_Color.a);
这样效果:
为立方体加上点光源
点光源顾名思义,就是由空间中一点发出的光,它只能照亮一小块区域,而且亮度和照射角
有关. 计算方式与平行光一样(色 x 光 x cosθ) 只不过点光源
的照射角
需要计算.(因为每一顶点与光源的角度都不同)
...
vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));
...
float nDotL = max(dot(lightDirection, normal), 0.0);
vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;
vec3 ambient = u_AmbientLight * a_Color.rgb;
v_Color = vec4(diffuse + ambient, a_Color.a);
下图为点光源下的立方体