重要概念:
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
1. 认识requestAnimationFrame()
在WebGL中,requestAnimationFrame()是一个非常重要的方法,它用于实现动画效果。看一下简单的动画代码示例:
function animate(timestamp) {
// 在这里编写动画逻辑
// 使用timestamp更新场景
// 递归调用animate函数以实现连续动画
requestAnimationFrame(animate);
}
// 启动动画循环
requestAnimationFrame(animate);
实现动画的精髓便在于此。比起使用setInterval()或setTimeout()相比,requestAnimationFrame()可以更好地节省系统资源。当页面处于后台或不可见时,它会自动暂停动画,从而减少不必要的计算开销。在回调函数中调用requestAnimationFrame()来实现连续的动画效果。这种递归调用可以让动画一直执行下去。
2. 简单的平移和缩放
在认识requestAnimationFrame()后,我们可以在上面的函数基础上增加动画逻辑,这里我们试下让一个三角形缩放。
在之前的案例中,有如下代码:
// vertex shader
let vertexShader = `
attribute vec2 a_position;
attribute vec3 a_color;
varying vec3 v_color;
void main() {
v_color = a_color;
gl_Position = vec4(a_position, 0.0, 1.0);
} `
这里的a_position表示的是x和y的坐标位置,进行平移和缩放则必定会更改在x方向和y方向的坐标点。
gl_Position = vec4(a_position.x+0.5,a_position.y+0.5, 0.0, 1.0);
上述代码中a_position.x+0.5,a_position.y+0.5,也可以将变化的值另外提取出来,通过四维坐标加上vec4(0.5, 0.5, 0.0, 1.0) 表示:
gl_Position = vec4(a_position, 0.0, 1.0) + vec4(0.5, 0.5, 0.0, 0.0);
做成动画,需要将其设置成一个变量,让位置信息进行改变。这里用到的变量为uniform,这里变量名取名u_translate :
// vertex shader
let vertexShader = `
attribute vec2 a_position;
uniform vec4 u_translate;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0) + u_translate;
gl_PointSize = 10.0;
}
`
这个u_translate 可以在动画中进行改变。
let tx = 0,ty = 0
function animate(timestamp) {
// 在这里编写动画逻辑
tx+=0.01;
ty+=0.01;
// 使用timestamp更新场景
let u_translate = gl.getUniformLocation(gl.program,'u_translate')
gl.uniform4f(u_translate,tx,ty,0.0,0.0)
draw(gl)
// 递归调用animate函数以实现连续动画
requestAnimationFrame(animate);
}
// 启动动画循环
animate();
实现效果也就是原图形沿着右上角逐渐平移出画面~~
简单的缩放可以将顶点着色器中进行改写,x和y坐标乘变量:
gl_Position = vec4(a_position.x*u_translate.x,a_position.y*u_translate.y, 0.0, 1.0) ;
实现效果是图形不停变大。
3. 认识矩阵
但是仅仅通过上述方式实现平移和缩放有点笨拙,且不好实现旋转的效果。引入新的概念矩阵 。 从矩阵的角度去理解位置改变,原来的点[x1,y1,z1]变换为[x2,y2,z2]。
推荐使用工具库glMatrix,安装成功后引入。
import { mat4,glMatrix } from 'gl-matrix'//按需引入
通过mat4.create() 创建矩阵matrix1,通过变化,生成矩阵matrix2。
//matrix1的x缩放Sx,y缩放Sy,z缩放Sz后,生成新的矩阵matrix2
mat4.scale(matrix2, matrix1, [Sx, Sy, Sz])
//[X, Y, Z]为[1,0,0]表示matrix1沿着x轴旋转deg度后,生成新的矩阵matrix2
//[X, Y, Z]为[0,1,0]表示matrix1沿着y轴旋转deg度后,生成新的矩阵matrix2
//[X, Y, Z]为[0,0,1]表示matrix1沿着z轴旋转deg度后,生成新的矩阵matrix2
mat4.rotate(matrix2, matrix1, deg, [X, Y, Z])
//下面三种方式,在函数中指明绕着哪个轴旋转
mat4.fromXRotation(rotate_matrix, deg)
mat4.fromRRotation(rotate_matrix, deg)
mat4.fromZRotation(rotate_matrix,deg)
//matrix1的x平移Tx,y平移Ty,z平移Tz后,生成新的矩阵matrix2
mat4.translate(matrix2, matrix1, [Tx, Ty, Tz])
如果只是对原有矩阵matrix1进行改变,也可以用如下方式:
mat4.fromScaling(matrix, [Sx, Sy, Sz])
mat4.fromTranslation(matrix, [Tx, Ty, Tz])
mat4.fromRotation(matrix, deg, [X, Y, Z])
4. 用矩阵库的方式进行矩阵变换
首先,需要在顶点着色器中定义一个mat4类型的变量u_matrix。
// vertex shader
let vertexShader = `
attribute vec2 a_position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * vec4(a_position, 0.0, 1.0);
gl_PointSize = 10.0;
}
`
uniformMatrix4fv(location, transpose, value)
location:目标矩阵,这里指顶点坐标器中的变量矩阵
transpose:一个 GL 布尔型变量,用于指定是否对矩阵进行转置。必须为 false。
平移,往x方向平移-0.5,y方向平移0.5:
let translate_matrix = mat4.create()
mat4.fromTranslation(translate_matrix, [-0.5, 0.5, 0])
let u_matrix = gl.getUniformLocation(gl.program, 'u_matrix')
gl.uniformMatrix4fv(u_matrix, false, translate_matrix)
缩放,x方向缩放0.5倍,y方向缩放0.5倍:
let scale_matrix = mat4.create()
mat4.fromScaling(scale_matrix, [0.5, 0.5, 1])
let u_matrix = gl.getUniformLocation(gl.program, 'u_matrix')
gl.uniformMatrix4fv(u_matrix, false, scale_matrix)
旋转,沿着z轴旋转10度:
let rotate_matrix = mat4.create()
mat4.rotate(rotate_matrix, rotate_matrix,10 / 180 * Math.PI, [0, 0, 1])
let u_matrix = gl.getUniformLocation(gl.program, 'u_matrix')
gl.uniformMatrix4fv(u_matrix, false, rotate_matrix)