WebGL入门:光照原理
一、计算物体的颜色
在日常生活中,我们见到的大多数物体都是基于漫反射光源的光线,从而被人眼看到的,就比如下面这张图

反射光的颜色即跟光源的颜色有关,也跟物体本身的颜色有关,颜色的强弱跟入射角也有关系,入射的光线角度不同时,单位面积所接受的能量是不一样的,越垂直照射越亮,基本符合余弦公式,所以得出如下的一个公式
漫反射颜色 = 入射光颜色 * 表面基地色 * cosθ
从WebGL数学手记:向量基础-CSDN博客 中可以知道
向量点乘公式:a
b = |a|
|b|
cosθ
如果向量 a和向量b都是单位向量的话,那么他们点乘的结果就是cosθ
假设一个物体的颜色是红色,rgb值是(1.0,0.0,0.0),而光源的颜色是白色(1.0,1.0,1.0),那么它俩计算出的颜色就是
表面基地颜色[1.0,0.0,0.0] * 入射光颜色[1.0,1.0,1.0] = [1.0,0.0,0.0]
考虑入射角的话就需要乘 cosθ ,假设角度是60度,cosθ=0.5,
表面基地颜色[1.0,0.0,0.0] * 入射光颜色[1.0,1.0,1.0] * cosθ = [0.5,0.0,0.0]
最终的颜色就是比较弱的红光
二、计算点光源下物体的颜色
从事上面的描述中,我们基本可以知道,要想知道物体的颜色,需要知道原物体的颜色,物体顶点位置的法线与光照方向的角度,所以在webgl中要提供顶点的法向量信息
// 创建立方体顶点const vertices = new Float32Array([// 前面1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0,// 后面1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, 1.0,-1.0,-1.0,// 顶面1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0,// 底面1.0,-1.0, 1.0, 1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0,// 右面1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0,// 左面-1.0, 1.0, 1.0, -1.0,-1.0, 1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0])// 立方体顶点的法线信息const 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,// 后面0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0,// 顶面0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,// 底面0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0,// 右面1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,// 左面-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0])
经过一系列的 gl.bindBuffer 和 gl.bufferData 后 进入顶点着色器
1.顶点着色器
const VSHADER_SOURCE = `attribute vec4 a_Position;attribute vec4 a_Normal;//模型矩阵、视图矩阵、投影矩阵uniform mat4 u_MvpMatrix;uniform mat4 u_ModelMatrix;//法线的逆转置矩阵(用于计算仿射变换后的物体法向量)uniform mat4 u_NormalMatrix;//世界坐标系下的法向量varying vec3 v_Normal;//世界坐标系下的坐标varying vec3 v_Position;void main() {gl_Position = u_MvpMatrix * a_Position;v_Position = vec3(u_ModelMatrix * a_Position);v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));}
`
这个顶点着色器中的关键信息有好多
1.1逆转置矩阵
仿射变换后的法向量 = 原法向量 * 模型矩阵的逆转置矩阵
1.2.Mvp矩阵
Mvp矩阵 = 模型矩阵 * 视图矩阵 * 投影矩阵
1.3.内插法向量
其实最简单的做法是在顶点着色器中根据顶点位置直接结算每个顶点对应的颜色值,然后由varying修饰符传递给片元着色器做内插,但是实际上这样的效果不是太逼真,所以一般的做法是在顶点着色器中计算顶点坐标的世界坐标系和世界坐标系下的法向量,然后用varying修饰符传递给片元着色器做内插,从片元着色器中计算光照方向更准确,也更逼真
2.片元着色器
const FSHADER_SOURCE = `precision mediump float;uniform vec3 u_LightPosition;uniform vec3 u_AmbientLight;uniform vec3 u_LightColor;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 * nDotL;vec3 ambient = u_AmbientLight;vec3 baseColor = vec3(0.8, 0.0, 0.0);gl_FragColor = vec4((diffuse + ambient) * baseColor, 1.0);}
`
片元着色器中的关键信息点如下
2.1 normalize 归一化
webgl提供的归一化方法,用于把向量转换成单位向量
2.2 lightDirection 光照方向
vec3 lightDirection = normalize(u_LightPosition - v_Position);
光照方向 = 光源向量 - 物体顶点位置
2.3 diffuse 漫反射光
漫反射颜色 = 入射光颜色 * 表面基地色 * cosθ
在不考虑表面基底色的情况下,由公式可以得到
float nDotL = max(dot(lightDirection, normal), 0.0);
vec3 diffuse = u_LightColor * nDotL;
2.4 环境光
实际生活中环境光是普遍存在的,比如太阳光,月光,星光等等,在大环境的范围内影响看到的物体,所以为了更贴近生活,一般在考虑实际光源的情况下,还要把环境中的光也考虑进去,所以最终的颜色是漫反射光加环境光
diffuse + ambient
最终的颜色是
vec3 baseColor = vec3(0.8, 0.0, 0.0);
gl_FragColor = vec4((diffuse + ambient) * baseColor, 1.0);
三、源码
传送门