前言
在之前的学习中已经将三维物体添加到了场景中,但是并没有在场景中使用光,照可以使模型更具有立体感,本文主要介绍 webgl 中的光照理论和计算方式,并展示如何在三维场景中添加光照。
光照理论介绍
光照效果
现实世界中的物体被光线照射时,会反射一部分光。只有当反射光线进入你的眼睛时,你才能够看到物体并辩认出它的颜色。比如,白色的盒子会反射白光,当白光进入你的眼睛时,你才能看到盒子是白色的。
当光线照射到物体上时,发生了两个重要的现象
- 根据光源和光线方向,物体不同表面的明暗程度变得不一致。
- 根据光源和光线方向,物体向地面投下了影子。
添加光源前:只有基本环境光
添加光源后
在片元着色(shading)时,不仅要根据片元着色器给像素上色,还要根据光照条件重建物体各表面明暗不一的效果
。物体向地面投下影子的现象,又被称为阴影(shadowing)。
光源类型
如图,光源主要有以下几种类型:
- 平行光(directional light),类似于自然中的太阳光;
平行光的光线是
相互平行
的,平行光具有方向
。平行光可以看作是无限远处的光源(比如太阳)发出的光。平行光很简单,只需要光照的方向和颜色
来定义。
- 点光源光(point light),类似于人造灯泡的光。
点光源光是从一个
点
向周围的所有方向
发出的光。点光源光可以用来表示现实中的灯泡、火焰等。我们需要指定点光源的位置和颜色
。光线的方向将根据点光源的位置和被照射之处的位置计算出来,因为点光源的光线的方向在场景内的不同位置是不同的。
- 环境光(ambient light),模拟真实世界中的非直射光(光源发出后经过墙壁或其他物体反射后的光)。致的“。比如说,在夜间打开冰箱的门,整个厨房都会有些微微亮,这就是环境光的作用。环境光不用指定位置和方向,只需要指定颜色即可。
环境光(间接光)是指那些经光源(点光源或平行光源)发出后,被墙壁等物体多次反射,然后照到物体表面上的光。环境光从各个角度照射物体,其
强度都是一致
的。比如说。环境光不用指定位置和方向
,只需要指定颜色
即可。
反射光颜色
物体向哪个方向反射光,反射的光是什么颜色,取决于以下两个因素:入射光
和物体表面的类型
。入射光的信息包括入射光的方向
和颜色
,而物体表面的信息包括表面的固有颜色
(也称基底色)和反射特性
。
物体表面反射光线的方式有两种:环境反射 和 漫反射
- 环境反射
环境反射是针对环境光而言
的。在环境反射中,反射光的方向可以认为就是入射光的反方向
。由于环境光照射物体的方式就是各方向均匀、强度相等的,所以反射光也是各向均匀
的。
<环境反射光颜色> = <入射光颜色> × <表面基底色>
- 漫反射
漫反射是针对平行光
或点光源
而言的。漫反射的反射光在各个方向上是均匀(强度相等)
的,现实中的大部分材质表面都是粗糙的,在这种情况下反射光就会以不固定的角度反射出去。漫反射就是针对这种情况而建立的理想反射模型。
在漫反射中,反射光的颜色取决于1 入射光的颜色
、2 表面的基底色
、3 入射光与表面形成的入射角
。入射角 θ 的定义为入射光与表面的法线形成的夹角
<漫反射光颜色> = <入射光颜色> × <表面基底色> × cos θ
= <入射光颜色> × <表面基底色> × ( <光线方向> * <法线方向>)
使用上面公式计算时,有两点需要特别注意:
1 <光线方向> 与 <法线方向> 两个矢量必须是经过归一化矢量的,即长度必须为1。
2 光线方向是入射方向的反方向,如下图。
- 漫反射 + 环境反射
在大多数情况下,漫反射和环境反射同时存在,二者相加就是物体映入人眼的真实颜色
<物体反射光颜色> = <漫反射光颜色> + <环境光反射颜色>
向场景中添加光
接前一文章:webgl三维绘制——彩色立方体,将物体的颜色改为纯色。
const vertex = `
attribute vec4 aPosition;
uniform mat4 uPerspectiveMatrix;
uniform mat4 uViewMatrix;
attribute vec4 aColor;
varying vec4 (1.0,0.0,0.0,1.0);
void main() {
gl_Position = uPerspectiveMatrix * uViewMatrix * aPosition ;
// v_Color = aPosition;
}
`
const fragment = `
precision highp float;
// varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`
向场景中添加环境光和点光源
为了实现添加光源后的效果,我们需要:
- 定义立方体每个面的法方向
- 在顶点着色器中添加点光源和环境光源,并按照之前的公式计算出点光源颜色和环境光源颜色
- 物体的真实颜色 = 点光源颜色 + 环境光颜色
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webgl</title>
<script src="./lib.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body {
margin: 0;
padding: 0;
}
canvas {
margin: 50px 30px;
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
/** @type {HTMLCanvasElement} */
//------------------------------------------------------创建画布
// 获取canvas元素对象
let canvas = document.getElementById('canvas');
let ctx = document.getElementById('canvas')
// 获取webgl绘图上下文
const gl = canvas.getContext('webgl');
if (!gl) {
throw new Error('WebGL not supported');
}
canvas.width = 500;
canvas.height = 500;
gl.viewport(0, 0, canvas.width, canvas.height)
// 设置背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.enable(gl.DEPTH_TEST);
// 清空缓冲区
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
const vertex = `
attribute vec4 aPosition; //
attribute vec4 aNormal; // 法方向
uniform mat4 uViewMatrix; // 视图矩阵
uniform mat4 uPerspectiveMatrix; // 投影矩阵
varying vec4 v_Color;
void main() {
vec4 cubeColor = vec4(1.0,0.0,0.0,1.0); // 物体颜色
vec3 lightColor = vec3(1.0, 1.0, 1.0); // 点光源颜色
vec3 lightPosition = vec3(5.0, 10.0, 10.0); // 点光源位置
vec3 ambientColor = vec3(0.1, 0.1, 0.1); // 环境光颜色
vec4 worldPosition = uPerspectiveMatrix * uViewMatrix * aPosition ; // 世界坐标
vec3 lightDirection = normalize(lightPosition - vec3(worldPosition)) ; // 点光源光束方向
vec3 ambientColors = ambientColor * vec3(cubeColor) ;// 环境光颜色
float deg = dot(lightDirection, vec3(aNormal));
vec3 diffColor = lightColor * vec3(cubeColor) * deg; // 漫反射光颜色
gl_Position = uPerspectiveMatrix * uViewMatrix * aPosition ;
v_Color = vec4(ambientColors + diffColor, 1.0); // 最终颜色
//v_Color = vec4(1.0,0.0,0.0,1.0); // 最终颜色
}
`
const fragment = `
precision highp float;
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`
// 创建program
const program = initShader(gl, vertex, fragment)
// 获取attribute变量的数据存储位置
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aColor = gl.getAttribLocation(program, 'aColor');
const aNormal = gl.getAttribLocation(program, 'aNormal');
// 获取uniform变量的数据存储位置
const uPerspectiveMatrix = gl.getUniformLocation(program, 'uPerspectiveMatrix');
const uViewMatrix = gl.getUniformLocation(program, 'uViewMatrix');
// 创建顶点缓冲区对象
const vertices = new Float32Array([
1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // 前面
1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // 右面
1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // 上面
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // 左面
-1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, // 下面
1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1 // 后面
])
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
const BYTES = vertices.BYTES_PER_ELEMENT;
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(aPosition)
// 创建索引缓冲区对象
const index = 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,// 后
]);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW)
// 向量
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 normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
const BYTES_NORMAL = normals.BYTES_PER_ELEMENT;
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW)
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(aNormal)
const viewMatrix = createViewMatrix(
new Float32Array([5.0, 5.0, 5.0]), // 视点位置
new Float32Array([0.0, 0.0, 0.0]), // 目标点位置
new Float32Array([0.0, 1.0, 0.0]), // 上方向
)
const perspectiveMatrix = createPerspective(30, ctx.width / ctx.height, 1, 100)
gl.uniformMatrix4fv(uPerspectiveMatrix, false, perspectiveMatrix,)
gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix,)
// 开始绘制
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_BYTE, 0)
</script>
</body>
</html>
逐片元光照——更加逼真
在片元着色器逐片元地进行计算光照产生的颜色,将会产生更加逼真的光照效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webgl</title>
<script src="./lib.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body {
margin: 0;
padding: 0;
}
canvas {
margin: 50px 30px;
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
/** @type {HTMLCanvasElement} */
//------------------------------------------------------创建画布
// 获取canvas元素对象
let canvas = document.getElementById('canvas');
let ctx = document.getElementById('canvas')
// 获取webgl绘图上下文
const gl = canvas.getContext('webgl');
if (!gl) {
throw new Error('WebGL not supported');
}
canvas.width = 500;
canvas.height = 500;
gl.viewport(0, 0, canvas.width, canvas.height)
// 设置背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.enable(gl.DEPTH_TEST);
// 清空缓冲区
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
const vertex = `
attribute vec4 aPosition; //
attribute vec4 aNormal; // 法方向
uniform mat4 uViewMatrix; // 视图矩阵
uniform mat4 uPerspectiveMatrix; // 投影矩阵
varying vec4 v_CubeColor; // 物体颜色
varying vec4 v_WorldPosition; // 世界坐标
varying vec4 v_Normal; // 法方向
void main() {
v_Normal = aNormal;
v_CubeColor = vec4(1.0,0.0,0.0,1.0); // 物体颜色
v_WorldPosition = uPerspectiveMatrix * uViewMatrix * aPosition ; // 世界坐标
gl_Position = uPerspectiveMatrix * uViewMatrix * aPosition ;
}
`
const fragment = `
precision highp float;
varying vec4 v_CubeColor; // 物体颜色
varying vec4 v_WorldPosition; // 世界坐标
varying vec4 v_Normal; // 法方向
void main(){
vec3 lightColor = vec3(1.0, 1.0, 1.0); // 点光源颜色
vec3 lightPosition = vec3(5.0, 10.0, 10.0); // 点光源位置
vec3 ambientColor = vec3(0.1, 0.1, 0.1); // 环境光颜色
vec3 lightDirection = normalize(lightPosition - vec3(v_WorldPosition)) ; // 点光源光束方向
vec3 ambientColors = ambientColor * vec3(v_CubeColor) ;// 环境光颜色
float deg = dot(lightDirection, vec3(v_Normal));
vec3 diffColor = lightColor * vec3(v_CubeColor) * deg; // 漫反射光颜色
gl_FragColor = vec4(ambientColors + diffColor, 1.0); // 最终颜色;
}
`
// 创建program
const program = initShader(gl, vertex, fragment)
// 获取attribute变量的数据存储位置
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aColor = gl.getAttribLocation(program, 'aColor');
const aNormal = gl.getAttribLocation(program, 'aNormal');
// 获取uniform变量的数据存储位置
const uPerspectiveMatrix = gl.getUniformLocation(program, 'uPerspectiveMatrix');
const uViewMatrix = gl.getUniformLocation(program, 'uViewMatrix');
// 创建顶点缓冲区对象
const vertices = new Float32Array([
1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // 前面
1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // 右面
1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // 上面
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // 左面
-1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, // 下面
1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1 // 后面
])
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
const BYTES = vertices.BYTES_PER_ELEMENT;
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(aPosition)
// 创建索引缓冲区对象
const index = 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,// 后
]);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW)
// 向量
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 normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
const BYTES_NORMAL = normals.BYTES_PER_ELEMENT;
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW)
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(aNormal)
const viewMatrix = createViewMatrix(
new Float32Array([5.0, 5.0, 5.0]), // 视点位置
new Float32Array([0.0, 0.0, 0.0]), // 目标点位置
new Float32Array([0.0, 1.0, 0.0]), // 上方向
)
const perspectiveMatrix = createPerspective(30, ctx.width / ctx.height, 1, 100)
gl.uniformMatrix4fv(uPerspectiveMatrix, false, perspectiveMatrix,)
gl.uniformMatrix4fv(uViewMatrix, false, viewMatrix,)
// 开始绘制
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_BYTE, 0)
</script>
</body>
</html>
总结
光照理论介绍
- 光照效果
- 光源类型
- 反射光颜色
向场景中添加光
- 向场景中添加环境光和点光源
- 逐片元光照——更加逼真