本篇文章主要对webgl光照进行相关解析,并不针对基础webgl知识。如果对基础webgl知识不甚了解的可以先查阅相关基础资料!
立方体的实现
我们先简要绘制一个立方体,看一下没有光照和阴影的效果。这里我简要列举部分代码进行示例:
// 首先展示定点着色器和片元着色器这部分代码
// 顶点着色器
const vertexShaderSource = `
attribute vec4 a_pos;
attribute vec4 a_color;
varying vec4 v_color;
void main(){
float radian = radians(25.0); // 旋转25度
float cos = cos(radian); // 求出余弦值
float sin = sin(radian); // 求出正弦值
//创建绕x轴旋转矩阵
// 1 0 0 0
// 0 cosα sinα 0
// 0 -sinα cosα 0
// 0 0 0 1
mat4 mx = mat4(1,0,0,0,0,cos,-sin,0,0,sin,cos,0,0,0,0,1);
//创建绕y轴旋转矩阵
// cosβ 0 sinβ 0
// 0 1 0 0
//-sinβ 0 cosβ 0
// 0 0 0 1
mat4 my = mat4(cos,0,-sin,0,0,1,0,0,sin,0,cos,0,0,0,0,1);
gl_Position = mx*my*a_pos;
v_color = a_color;
}
`
// 片元着色器
const fragShaderSource = `
precision lowp float;
varying vec4 v_color;
void main(){
gl_FragColor = v_color;
}
`
复制代码
传入顶点数据之后渲染的结果如下:
由上图渲染结果我们仅能看出立方体的大概,它并没有表现出明显的3D样式,从这里我们就可以引出光照的概念来解释其原由。
光照的实现
光照的物理模型
在生活中,我们能够看到物体是因为它被光线照射然后反射一部分光线到了我们的眼睛里。因为光源和光线方向等等因素导致了最后你看到的物体出现了明暗差异,也正是因为这些明暗的差异让我们感受到了物体的立体感,上面绘制出的立方体之所以看不出明显的立体感就是因为没有考虑光照的阴影。
现在我们尝试在webgl中实现光照,在此之前首先需要区分一下光源类型:平行光(一般指太阳光),电光源(也就是灯泡这类光源),环境光(由光源发出后经过墙壁或其他物体反射后的光),如下图所示:
同样的还需要区分一下反射类型,一般物理学中通常分为这两大类:漫反射以及镜面反射。物理模型如下图所示:
获取计算模型
这些物理模型大家应该都在初中物理课上看过,也非常好理解。现在我就解释一下如何将以上物理模型融合进webgl里面,用算法来实现真实的物理效果。这里拿漫反射来举例:
由物理模型可以看出我们取到入射光线与顶点法线向量的夹角,如果入射角垂直于顶点法线也就是90度,那么则与该顶点平面平行,入射光线也就不可能照射到顶点上或者说不可能有反射光到我们的眼睛中,所以此时的顶点颜色不管是什么颜色都应该是纯黑色,用rgb也就是rgb(0,0,0)
。
由上述分析很明显颜色的计算模型为下面公式
漫反射反射光的颜色 = 几何体表面基色 x 光线颜色 x 光线入射角余弦值
webgl中的实现
得到上述计算模型之后,我们就可以在webgl中实现光照了。
同样的我们还是先写着色器代码
// 顶点着色器
const vertexShaderSource = `
attribute vec4 a_pos; // 顶点变量
attribute vec4 a_color; // 顶点颜色变量
attribute vec4 a_normal; // 法向量
uniform vec3 u_lightColor; // 平行光颜色
uniform vec3 u_lightDirection; // 平行光方向
varying vec4 v_color;
void main(){
float radian = radians(25.0); // 旋转25度
float cos = cos(radian); // 求出余弦值
float sin = sin(radian); // 求出正弦值
//创建绕x轴旋转矩阵
// 1 0 0 0
// 0 cosα sinα 0
// 0 -sinα cosα 0
// 0 0 0 1
mat4 mx = mat4(1,0,0,0,0,cos,-sin,0,0,sin,cos,0,0,0,0,1);
//创建绕y轴旋转矩阵
// cosβ 0 sinβ 0
// 0 1 0 0
//-sinβ 0 cosβ 0
// 0 0 0 1
mat4 my = mat4(cos,0,-sin,0,0,1,0,0,sin,0,cos,0,0,0,0,1);
gl_Position = mx*my*a_pos;
vec3 normal = normalize((mx*my*a_normal).xyz); // 求顶点法向量归一化 也就是求单位向量
float dot = max(dot(u_lightDirection, normal), 0.0); // 计算平行光方向向量和顶点法向量的点积 也就是他们夹角的余弦值
vec3 reflectedLight = u_lightColor * a_color.rgb * dot; // 此处便是上面得到的数学模型
v_color = vec4(reflectedLight, a_color.a); // 透明度插值
}
`
// 片元着色器
const fragShaderSource = `
precision lowp float;
varying vec4 v_color;
void main(){
gl_FragColor = v_color;
}
`
复制代码
根据着色器代码,我们需要传入顶点法向量,入射光方向以及入射光颜色,即可得到有光照的渲染模型,如下所示:
完美!