1. 光照和反射
要知道看到的物体的颜色实际上是物体反射的光的颜色,物体吸收了部分频率的光,将不能吸收的光进行了反射,从而我们看到了对应物体呈现的颜色。
光照对于构建一个三维图形有着很大的影响,所以首先一起来讨论一下基本的光照类型以及反射类型。
1.1 光照类型
光照类型有很多,根据光源的不同以及发射出来的光线的特性,有三种最常用的:平行光源,点光源,环境光源
1.1.1 平行光源
平行光源光线是平行的,太阳光可以认为是平行光源。其实将一个点光源放在无限远处,那么最后我们所看到的光基本就是处于平行的,所以平行光源是点光源的极限状态,同一表面上点对光的处理是一样的(反射角度一样)
1.1.2 点光源
点光源的特点就是光源是点状,发射出来的光线角度是不一致的,同一表面上每个点对光线的处理会不同。
1.1.3 环境光源
环境光源是墙面反射光线以后照射到物体表面的光,环境光对于物体所有表面每一个点增加的强度可以认为是一致的。
1.2 反射类型
1.2.1 漫反射
漫反射是针对平行光源和点光源而言,特点就是将物体默认为是类似镜面的物体,所有的光会以固定的角度反射回去。漫反射是一种理想的反射条件,忽略了物体本身材质的影响(例如岩石,纸张会是斑驳的,现实中没有完全的镜面,反射角度都不是固定的)
对于漫反射而言,我们看到的物体所呈现的颜色,其实就是:
漫反射颜色 = 入射光颜色 * 物体表面基底色 * cosx
其中的cosx是指入射光与物体表面的夹角。
1.2.2 环境反射
环境反射是针对环境光源而言,可以认为光线均匀的铺在物体表面,物体反射出的光线无论什么角度强度都是一样的,其计算公式为:
环境反射颜色 = 入射光颜色 * 物体表面基底色
PS:于是最后物体反射的颜色(反射的颜色也就是我们看到的颜色)公式:物体反射颜色 = 漫反射颜色 + 环境反射颜色
2. 光照计算
于是在在WebGL中,根据光照类型以及对应光照类型的反射特性来计算对应物体的光照效果
2.1 平行光计算
平行光的反射类型是漫反射,所以根据漫反射公式,如果要计算物体表面颜色,那么需要指定到三个数据:入射光颜色,物体表面基底色,入射光角度。
入射光颜色我们和物体表面基底色在我们创建光源和物体表面的时候就可以进行指定,于是主要需要计算的部分就是入射光角度。
平行光的入射光角度是光源和物体表面的夹角,要根据入射光方向和物体表面朝向(法线方向)计算,其实并不容易。不过所幸可以通过矢量的点积的方式来计算出对应入射角的余弦值(自己线性代数基本上忘了差不多了,补习中,不能很好说明为什么矢量的点积可以计算出这个结果)。
于是说了那么多就变成了一下几点:
- 获取到表面的法线方向
- 光线方向和法线方向计算点积
也就是最后的计算公式就是
平行光漫反射 = 入射光颜色 * 物体表面基底色 * dot( 光线方向 * 法线方向)
以正方体且法线方向就是x,y,z轴的正负方向为例,那么着色器中需要做的就是将我们的计算公式:
// VertexShader中增加
void main () {
...
attribute vec4 a_Normal; // 法向量
uniform vec3 u_LightDIrection; // 光线方向(归一化)
uniform vec3 u_LightColor; // 光线颜色
// 法向量归一化
vec3 normal = normalize(vec3(a_Normal))
// 点积计算
float nDotL = max(dot(u_LightDirection, normal), 0.0);
// 颜色计算
vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL
v_Color = vec4(diffuse, a_Color.a);
}
需要注意的是这里的在进行点积计算的时候,法线和光线方向一定都要首先经过归一化,变成单位矢量后进行计算,所以u_LightDirection
也要进行归一化处理
2.2 环境光的叠加
环境光之前说是均匀的,所以环境光只需要入射光颜色和物体表面颜色进行确定,所以如果叠加上环境光,反射光的计算公式就变为:
漫反射 = 入射光颜色 * 物体表面基底色 * dot( 光线方向 * 法线方向)+ 环境入射光颜色 * 物体表面基底色
所以顶点着色器中继续增加环境光只需要
...
uniform vec3 u_AmbientLight;
vec3 ambient = u_AmbientLight * a_Color.rgb;
v_Color = vec4(diffuse + ambient, a_Color.a);
...
2.3 点光源点处理
点光源其实原理和平行光源类似,只是由于点光源的每个点的反射角度不同,所以不设定一个统一的光线方向,需要将平行光的光源方向变量u_LightDirection
替换为,光源坐标和表面顶点位置的矢量计算结果
// VertexShader中增加
void main () {
...
uniform vec3 u_LightPosition; // 光源位置
...
// 计算光线方向
vec3 lightDirection = normalize(u_LightPosition - vec3(a_Position))
// 将u_LightDirection替换为lightDirection进行点积计算
float nDotL = max(dot(lightDirection, normal), 0.0);
}
2.4 片元着色器内插队光照计算的影响
刚才都是将计算放在顶点着色器内进行,但是由于片元着色器的内插过程影响,所以如果在顶点着色器内渲染,内插过程会出现偏差失真,于是,要将在顶点着色器中渲染的内容放到片元着色器中通过顶点和法向量的内插值进行计算。
// VertexShader中计算光线方向和法线方向
void main () {
...
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec4 v_Color;
// 计算光线方向
v_Position = a_Position;
v_Normal = normalize(a_Normal);
v_Color = a_Color;
}
// FragmentShader色器
void main () {
...
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec4 v_Color;
uniform vec3 u_LightPosition; // 光源位置
uniform vec3 u_LightColor; // 光线颜色
// 法向量归一化
vec3 normal = normalize(v_Normal)
// 计算光线方向
vec3 lightDirection = normalize(u_LightPosition - v_Position)
// 将u_LightDirection替换为lightDirection进行点积计算
float nDotL = max(dot(lightDirection, normal), 0.0);
// 颜色计算
vec3 diffuse = u_LightColor * vec3(v_Color) * nDotL
gl_FragColor = vec4(diffuse, v_Color.a);
}
3. 总结
总的来说要计算光照对物体的影响,需要获取到光线方向(光源位置)和法线方向,通过物体漫反射光计算公式进行计算
这是基本上WebGL入门学习笔记的最后一期,虽然还有很多学习到的其他特性并没有记录,但是在接下来会用各种具体实现的事例的方式来解决具体问题
4. 参考
《WebGL编程指南》
网友评论