WebGL系列(5):光照
前一篇文章WebGL系列(4):开启三维世界中最后描述了如何绘制一个立方体。但是绘制出来的立方体看起来仍有点差强人意。如下图,是一个纯白色的立方体,每个面颜色都相同。但是在现实世界中,即使每个面的颜色相同,但是我们所观察到的每个面还是有所差异。这就是因为光照的原因,本文就来学习一下光照。
一、光照原理
现实世界中的物体被光线照射时,会反射一部分光。只有当反射光线进入眼睛时,才能看到物体并辨认出它的颜色。当光线照射到物体上时,发生了两个现象:
- 根据光源和光线方向,物体不同表面的明暗程度变得不一致
- 根据光源和光线方向,物体向地面投下了影子。
三维图形学中,着色的真正含义是:根据光照条件重建“物体各表面明暗不一效果”的过程。物体向地面投下影子的现象,又被称为阴影。
在深入了解之前,我们先来看看,发出光线的光源有哪些以及物体表面如何反射光线。
1.1 光源类型
真实世界中的光主要有两种类型:
- 平行光(directional light):光线相互平行,且具有方向。平行光可以看作是无限远处的光源发出的光,例如太阳光。平行光可以用 一个方向 和 一个颜色 来定义。
- 点光源光(point light):点光源光是一个点向周围所有方向发出的光,例如人造灯泡的光。需要指定点光源的位置和颜色。光线的方向将根据点光源的位置和被照射之处的位置计算出来,因为点光源的光线的方向在场景内的不同位置是不同的。
除了上述两种类型之外,我们还用环境光来模拟真实世界中的非直射光,
- 环境光(ambient):指那些光源(点光源或平行光源)发出后,被墙壁等物体多次反射,然后照到物体表面上的光。环境光从各个角度照射物体,其强度都是一致的。环境光不用指定位置和方向,只需指定颜色即可。
实际上,环境光是各种光被各种表面经过多次反射后形成,我们认为环境光是“均匀”照射到物体表面的,因为没有必要去精确计算环境光的产生过程。
1.2 反射类型
物体向哪个方向反射光,反射的光是什么颜色,取决于以下两个因素:
- 入射光:包括入射光的方向和颜色
- 物体表面的类型:包括表面的固有颜色和反射特性
物体表面反射光线的方式有两种:漫反射(diffuse reflection) 和 环境反射(environment/ambient reflection)
(1)漫反射
漫反射针对平行光或点光源而言。漫反射的反射光在各个方向上是均匀的。
在漫反射中,反射光的颜色取决于入射光的颜色、表面的基底色、入射光与表面形成的入射角。我们将入射角定义为入射光与表面法线形成的夹角(θ\thetaθ),那么漫反射光的颜色可根据如下公式计算得出:
上述公式中的入射角θ\thetaθ,我们想要获取这个角度并不是那么简单,但是我们可以通过另一种方式来计算。例如我们有两个向量 a⃗\vec{a}a 和 b⃗\vec{b}b,那么:
对上式进行换算可得:
那么当∣a⃗∣|\vec{a}|∣a∣ 和 ∣b⃗∣|\vec{b}|∣b∣ 的值为 1 时,那么就有:
应用到计算漫反射光颜色的式子中,a⃗\vec{a}a 就是光线方向,b⃗\vec{b}b 就是法线方向,对光线方向和法线方向的向量进行归一化处理后,就可得到如下公式:
注意:上述公式中的 ×\times× 和 ⋅\cdot⋅ 运算不同,例如两个向量a⃗=(x1,y1,z1)\vec{a}=(x_1, y_1, z_1)a=(x1,y1,z1) 和 a⃗=(x2,y2,z2)\vec{a}=(x_2, y_2, z_2)a=(x2,y2,z2)
a⃗×b⃗=(x1∗x2,y1∗y2,z1∗z2)\vec{a} \times \vec{b}=(x_1 * x_2, y_1 * y_2, z_1 * z_2)a×b=(x1∗x2,y1∗y2,z1∗z2)
a⃗⋅b⃗=x1∗x2+y1∗y2+z1∗z2\vec{a} \cdot \vec{b}=x_1 * x_2 + y_1 * y_2 + z_1 * z_2a⋅b=x1∗x2+y1∗y2+z1∗z2
此外:∣a⃗∣=x12+y12+z12|\vec{a}|=\sqrt{x_1^2 + y_1^2 + z_1^2}∣a∣=x12+y12+z12
关于法向量,就是物体表面的朝向,即垂直于表面的方向。通常每个表面都有正面和反面两面,两个面各自具有一个法向量。在三维图形学中,表面的正面和反面取决于绘制表面顶点的顺序,可用右手法则来确定法线方向。
(2)环境反射
环境反射针对环境光而言。在环境反射中,反射光的方向可以认为是入射光的反方向。由于环境光照射物体的方式是个方向均匀、强度相等的,所以反射光也是个方向均匀的,可以用如下公式描述:
当 漫反射 和 环境反射 同时存在时,两者加起来,就是物体最终被观察到的颜色:
二、平行光
下面我们就来绘制一个处于白色平行光照射下的红色三角形
function initArrayBuffer(gl, attribute, data, num, type) {
// 创建坐标缓冲区对象
const buffer = gl.createBuffer();
// 将数据写入缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// 将缓冲区对象分配给 attribute 变量
const a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log(`获取 ${attribute} 的存储位置失败!`);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// 将缓冲区对象分配给 attribute 变量
gl.enableVertexAttribArray(a_attribute);
return true;
}
// 初始化顶点缓冲区
function initVertexBuffer(gl) {
// 顶点坐标
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 colors = new Float32Array([
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,
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,
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
])
// 法向量
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,
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.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,
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, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
])
// 顶点索引
const indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // 前
4, 5, 6, 4, 6, 7, // 右
8, 9, 10, 8, 10, 11, // 上
12, 13, 14, 12, 14, 15, // 左
16, 17, 18, 16, 18, 19,// 下
20, 21, 22, 20, 22, 23 // 后
])
// 坐标信息写入缓冲区
if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) {
return -1;
}
// 颜色信息写入缓冲区
if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) {
return -1;
}
// 法向量写入缓冲区
if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) {
return -1;
}
// 创建索引缓冲区
const indexBuffer =gl.createBuffer();
if (!indexBuffer) {
console.log('创建索引缓冲区对象失败!');
return -1;
}
// 将顶点索引数据写入缓冲区对象
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
// 顶点着色器程序
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' + // 法向量
'uniform mat4 u_MvpMatrix;\n' +
'uniform vec3 u_LightColor;\n' + // 光线颜色
'uniform vec3 u_LightDirection;\n' + // 归一化世界坐标
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
// 对法向量进行归一化
' vec3 normal = normalize(vec3(a_Normal));\n' +
// 计算光线方向和法向量点积
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
// 计算漫反射光的颜色
' vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
' v_Color = vec4(diffuse, a_Color.a);\n' + // 将数据传递给片元着色器
'}\n';
const FSHADER_SOURCE =
'precision mediump float;\n' +
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_FragColor = v_Color;\n' + // 从顶点着色器接收数据
'}\n';
function main() {
// 获取canvas元素
const canvas = document.getElementById('gl');
// 获取WebGL绘图上下文
const gl = canvas.getContext('webgl');
// 确认WebGL支持性
if (!gl) {
console.log('浏览器不支持WebGL');
return;
}
// 初始化着色器
if(!initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('初始化着色器失败!');
return;
}
// 设置顶点位置
const n = initVertexBuffer(gl);
if (n < 0) {
console.log('设置顶点位置失败!');
return;
}
// 获取 u_LightColor 变量的存储地址
const u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
if (u_LightColor < 0) {
console.log('u_LightColor 变量的存储地址获取失败!');
return;
}
// 获取 u_LightDirection 变量的存储地址
const u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
if (u_LightDirection < 0) {
console.log('u_LightDirection 变量的存储地址获取失败!');
return;
}
// 获取 u_MvpMatrix 变量的存储地址
const u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
if (u_MvpMatrix < 0) {
console.log('u_MvpMatrix 变量的存储地址获取失败!');
return;
}
// 设置光线颜色
gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
// 设置光线方向
const lightDirection = new Vector3([0.5, 3.0, 4.0]);
lightDirection.normalize(); // 归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);
// 设置视点、视线和上方向
const mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, canvas.clientWidth / canvas.clientHeight, 1, 100)
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0)
// 将试图矩阵传递给 u_MvpMatrix 变量
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
// 设置canvas背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// 情况canvas
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 绘制
gl.drawElements(gl.TRIANGLES , n, gl.UNSIGNED_BYTE, 0);
}
我们首先来看顶点着色器中的代码:
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' + // 法向量
'uniform mat4 u_MvpMatrix;\n' +
'uniform vec3 u_LightColor;\n' + // 光线颜色
'uniform vec3 u_LightDirection;\n' + // 归一化的世界坐标
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
// 对法向量进行归一化
' vec3 normal = normalize(vec3(a_Normal));\n' +
// 计算光线方向和法向量点积
' float nDotL = max(dot(u_LightDirection, normal),0.0);\n' +
// 计算漫反射光的颜色
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' +
// 将数据传递给片元着色器
' v_Color = vec4(diffuse, a_Color.a);\n' +
'}\n';
a_Color
:表面的基底色a_Normal
:表面法线方向u_LightColor
:入射光颜色u_LightDirection
:入射光方向
max(dot(u_LightDirection, normal)
用于计算<光线方向><法线方向><光线方向><法线方向><光线方向><法线方向>,如果点积大于0,就将点积赋值给nDotL变量,如果小于0,则赋值0.
点积值小于0,即 cosθcos\thetacosθ值小于0,也就意味着入射角θ\thetaθ大于90度。θ\thetaθ大于90度说明光线照射在表面的背面上。
上述图片中立方体的侧面是黑色的,几乎看不见颜色,但是实际并非如此。那些背光的面是被非直射光照亮的,即环境光。前面也说到:
所以我们在程序中加入环境光的照射,再来看看效果:
// 顶点着色器程序
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' + // 法向量
'uniform mat4 u_MvpMatrix;\n' +
'uniform vec3 u_LightColor;\n' + // 光线颜色
'uniform vec3 u_LightDirection;\n' + // 归一化世界坐标
'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
// 对法向量进行归一化
' vec3 normal = normalize(vec3(a_Normal));\n' +
// 计算光线方向和法向量点积
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
// 计算漫反射光的颜色
' vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
// 计算环境光产生的反射光颜色
' vec3 ambient = u_AmbientLight * a_Color.rgb;\n' +
' v_Color = vec4(diffuse + ambient, a_Color.a);\n' + // 将数据传递给片元着色器
'}\n';
// 获取 u_AmbientLight 变量的存储地址
const u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
if (u_AmbientLight < 0) {
console.log('u_AmbientLight 变量的存储地址获取失败!');
return;
}
// 设置环境光
gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
如图所示,完全没有被平行光照射到的表面不是全黑,而是呈现较暗的颜色,与真实世界更加相符。
物体变换后的光照效果
前面我们说过,图形的有平移、旋转、缩放三种变换。它们会对法向量有如下影响:
- 平移:不会改变法向量,因为平移不会改变物体的方向
- 旋转:会改变法向量,因为旋转改变了物体的方向
- 缩放:视情况而定(某些会改变,某些不会改变)
这些变换对于法向量的影响,我们可以通过魔法矩阵——逆转置矩阵来计算。假设各种变换矩阵相乘得到矩阵为模型矩阵,那么变换后的法向量,只需将变换之前的法向量乘以模型矩阵的逆转置矩阵(inverse transpose matrix) 即可。
假设,一个矩阵先进行平移变换,再进行旋转变换,那么最后的法向量计算如下公式所示。
逆矩阵:如果矩阵M的逆矩阵为R,那么 R*M 和 M*R 都是单位矩阵
逆转置矩阵:逆矩阵的转置矩阵
下面我们看一个例子:
// 顶点着色器程序
const VSHADER_SOURCE =
...
'uniform mat4 u_NormalMatrix;\n' + // 用来变换法向量的矩阵
...
// 计算变换后的法向量并归一化
' vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
...
function main() {
...
// 获取 u_NormalMatrix 变量的存储地址
const u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
if (u_NormalMatrix < 0) {
console.log('u_NormalMatrix 变量的存储地址获取失败!');
return;
}
...
const modelMatrix = new Matrix4(); // 模型矩阵
const normalMatrix = new Matrix4(); // 用于变换法向量的矩阵
// 计算模型矩阵
modelMatrix.setTranslate(0, 1, 0); // 绕Y轴平移
modelMatrix.rotate(90, 0, 0, 1); // 绕Z轴旋转
// 根据模型矩阵计算变换法向量的矩阵
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();
// 将变换法向量的矩阵传递给 u_NormalMatrix 变量
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
...
}
三、点光源光
点光源发出的光,在三维空间的不同位置上其方向也不同。对点光源光的物体进行着色时,需要每个入射点计算点光源光所处的方向。
// 顶点着色器程序
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' + // 法向量
'uniform mat4 u_ModelMatrix;\n' + // 模型矩阵
'uniform mat4 u_MvpMatrix;\n' +
'uniform mat4 u_NormalMatrix;\n' + // 用来变换法向量的矩阵
'uniform vec3 u_LightColor;\n' + // 光线颜色
'uniform vec3 u_LightPosition;\n' + // 光源位置
'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
// 计算变换后的法向量并归一化
' vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
// 计算顶点的世界坐标
' vec4 vertexPosition = u_ModelMatrix * a_Position;\n' +
// 计算光线方向并归一化
' vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));\n' +
// 计算光线方向和法向量点积
' float nDotL = max(dot(lightDirection, normal), 0.0);\n' +
// 计算漫反射光的颜色
' vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
// 计算环境光产生的反射光颜色
' vec3 ambient = u_AmbientLight * a_Color.rgb;\n' +
' v_Color = vec4(diffuse + ambient, a_Color.a);\n' + // 将数据传递给片元着色器
'}\n';
const FSHADER_SOURCE =
'precision mediump float;\n' +
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
' gl_FragColor = v_Color;\n' + // 从顶点着色器接收数据
'}\n';
function main() {
...
// 获取 u_NormalMatrix 变量的存储地址
const u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
if (u_NormalMatrix < 0) {
console.log('u_NormalMatrix 变量的存储地址获取失败!');
return;
}
// 获取 u_ModelMatrix 变量的存储地址
const u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
if (u_ModelMatrix < 0) {
console.log('u_ModelMatrix 变量的存储地址获取失败!');
return;
}
// 获取 u_LightPosition 变量的存储地址
const u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
if (u_LightPosition < 0) {
console.log('u_LightPosition 变量的存储地址获取失败!');
return;
}
// 设置光线颜色
gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
// 设置光线方向
gl.uniform3f(u_LightPosition, 0.0, 3.0, 4.0);
// 设置环境光
gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
const modelMatrix = new Matrix4(); // 模型矩阵
const normalMatrix = new Matrix4(); // 用于变换法向量的矩阵
// 计算模型矩阵
modelMatrix.rotate(90, 0, 0, 1); // 绕Z轴旋转
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
// 根据模型矩阵计算变换法向量的矩阵
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();
// 将变换法向量的矩阵传递给 u_NormalMatrix 变量
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
// 设置视点、视线和上方向
const mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, canvas.clientWidth / canvas.clientHeight, 1, 100)
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0)
// 将试图矩阵传递给 u_MvpMatrix 变量
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
...
}
四、逐片元计算光照
为了逐片元计算光照,需要知道:
- 片元在世界坐标系下的坐标
- 片元处表面的法向量
可以在顶点着色器中,将顶点的世界坐标和法向量以varying变量的形式传入片元着色器,片元着色器中的同名变量就已经是内插后的逐片元值了。
// 顶点着色器程序
const VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' + // 法向量
'uniform mat4 u_ModelMatrix;\n' + // 模型矩阵
'uniform mat4 u_MvpMatrix;\n' +
'uniform mat4 u_NormalMatrix;\n' + // 用来变换法向量的矩阵
'varying vec4 v_Color;\n' + // varying 变量
'varying vec3 v_Normal;\n' +
'varying vec3 v_Position;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
// 计算顶点的世界坐标
' v_Position = vec3(u_ModelMatrix * a_Position);\n' +
// 计算变换后的法向量并归一化
' v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));\n' +
' v_Color = a_Color;\n' +
'}\n';
const FSHADER_SOURCE =
'precision mediump float;\n' +
'uniform vec3 u_LightColor;\n' + // 光线颜色
'uniform vec3 u_LightPosition;\n' + // 光源位置
'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
'varying vec3 v_Normal;\n' +
'varying vec3 v_Position;\n' +
'varying vec4 v_Color;\n' + // varying 变量
'void main() {\n' +
// 对法线进行归一化,因为其内插后长度不一定是1.0
' vec3 normal = normalize(v_Normal);\n' +
// 计算光线方向并归一化
' vec3 lightDirection = normalize(u_LightPosition - v_Position);\n' +
// 计算光线方向和法向量点积
' float nDotL = max(dot(lightDirection, normal), 0.0);\n' +
// 计算漫反射光的颜色
' vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;\n' +
// 计算环境光产生的反射光颜色
' vec3 ambient = u_AmbientLight * v_Color.rgb;\n' +
' gl_FragColor = vec4(diffuse + ambient, v_Color.a);\n' + // 从顶点着色器接收数据
'}\n';
本文就到这里了,主要介绍了在光照效果下,三维场景更加逼真。
参考:
[1] 《WebGL 编程指南》
转载自:https://juejin.cn/post/7158633464622678030