webgl(五)简易动画和矩阵库的使用

308 阅读4分钟

重要概念: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)