【WebGL之巅】14-简单动画-使三角形旋转

By 大Van家 on 2021-07-27
阅读时间 9 分钟
文章共 2.1k
阅读量

对应《WebGL编程指南》代码:15-RotatingTriangle

要点:矩阵变换库(cuon-matrix.js 本书专用)、Matrix4对象、setRotate方法

tips:当前页含有动画示例,Tag Cloud可能会失效。

知识点

一、动画原理

基本原理:不断擦除和重绘三角形,并且每次重绘时轻微改变其角度

为了生成动画,需要两个关键机制:

​ 机制一:在规定时刻反复调用同一个函数来绘制三角形。

​ 机制二:在每次绘制之前,清除上次绘制的内容,并使三角形旋转相应的角度

​ 另外:由于程序需要反复绘制三角形,所有提前指定背景色,而不是在进行绘制之前(设置好的背景色在重设之前一直有效)

二、反复调用绘制函数(tick())
1
2
3
4
5
6
var tick = function () {
currentAngle = animate(currentAngle); // 更新旋转角
draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix); // 绘制三角形
requestAnimationFrame(tick); // 请求浏览器调用tick
};
tick();
三、按照指定的旋转角度绘制三角形(draw())
参数
gl 绘制三角形的上下文
n 顶点个数
currentAngle 当前的旋转角度
modelMatrix 根据当前的旋转角度计算出的旋转矩阵,存储在matrix4对象中
u_ModelMatrix 顶点着色器中同名的uniform变量的存储位置,modelMatrix变量将被传递至此处
1
2
3
4
5
6
7
8
9
10
11
function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix)
{
// 设置旋转矩阵
modelMatrix.setRotate(currentAngle, 0, 0, 1);
// 将旋转矩阵传输给顶点着色器
gl.uniformMatrix4fv( u_ModelMatrix, false, modelMatrix.elements);
// 清除canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, n);
}
四、请求再次被调用(requestAnimationFrame(tick))

按传统习惯,JavaScript想要重复执行某个任务可以使用setInterval()函数。

现代浏览器都支持多个标签页,每个标签页具有单独JavaScript环境,但是自setInterval()诞生之初,浏览器还没有支持多标签页,所以现代浏览器中,不管标签页是否被激活,其中的setInterval()函数都会反复调用func,如果标签页过多,就会增加浏览器负荷。于是引入了requestAnimationFrame()方法,该方法只有当标签页激活时才触发。注意,该函数无法指定重复调用的间隔,传入的函数func会在浏览器需要网页的某个元素重绘时被调用,其机制更像setTimeOut,想要再次调用就必须重新发起一次请求

五、更新旋转角(animate())

函数逻辑:根据本次调用与上次调用之间的时间间隔来决定这一帧的旋转角度比上一帧大出多少。

why?requestAnimationFrame只是请求浏览器在适当的时机调用参数函数,那么浏览器就会根据自身状态决定每次调用的时刻,在不同浏览器上,或在同一浏览器不同状态下,都有所不同。既然调用tick函数的间隔不恒定,那么每次调用时简单的向currentAngle加上一个固定角度值(度/秒)就会导致不可控的加速或减速的旋转效果。

currentAngle: 三角形当前旋转角度,即从初始位置算起,当前三角形旋转了多少度。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 记录上一次调用函数的时刻
var g_last = Date.now(); // 上一帧
function animate(angle)
{
// 计算距离上一次调用经过多长时间
var now = Date.now(); // 这一帧
var elapsed = now - g_last; // 毫秒
g_last = now; // 更新调用时间,下一次调用时刻与该时刻做比较
// 根据距离上次调用的时间,更新当前的旋转角度
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
// 保证newAngle始终小于360度
return newAngle %= 360;
}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>rotate triangle</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use the browser supporting "canvas".
</canvas>

<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script src="RotatingTriangle.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//RotatingTriangle.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'void main() {\n' +
'gl_Position = u_ModelMatrix * a_Position;\n' +
'}\n';

var FSHADER_SOURCE=
'void main(){'+
'gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);'+
'}';

// 旋转速度,单位:度/秒
var ANGLE_STEP = 40.0;

function main() {

var canvas = document.getElementById("webgl");
if (!canvas) {
console.log("Failed to retrieve the <canvas> element");
return;
}

var gl = getWebGLContext(canvas);

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

// 设置顶点位置
var n = initVertexBuffers(gl);

// 提前设置canvas背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);

var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');

// 模型矩阵
var modelMatrix = new Matrix4();

// 三角形当前角度
var currentAngle = 0.0;

// 开始绘制三角形
var tick = function () {
currentAngle = animate(currentAngle); // 更新旋转角
draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix); // 绘制三角形
requestAnimationFrame(tick); // 请求浏览器调用tick
};
tick();
}

function initVertexBuffers(gl) {
var vertices = new Float32Array(
[0.0, 0.5, -0.5, -0.5, 0.5, -0.5]
);
var n=3; //点的个数

//创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer){
console.log("Failed to create thie buffer object");
return -1;
}

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if(a_Position < 0){
console.log("Failed to get the storage location of a_Position");
return -1;
}

//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

return n;
}

function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix)
{
// 设置旋转矩阵
modelMatrix.setRotate(currentAngle, 0, 0, 1);
modelMatrix.translate(0.35, 0, 0)
// 将旋转矩阵传输给顶点着色器
gl.uniformMatrix4fv( u_ModelMatrix, false, modelMatrix.elements);
// 清除canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, n);
}

// 记录上一次调用函数的时刻
var g_last = Date.now();
function animate(angle)
{
// 计算距离上一次调用经过多长时间
var now = Date.now();
var elapsed = now - g_last; // 毫秒
g_last = now; // 更新调用时间,下一次调用时刻与该时刻做比较
// 根据距离上次调用的时间,更新当前的旋转角度
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360; // 保证newAngle始终小于360度
}

function up(){
ANGLE_STEP += 10
if (ANGLE_STEP > 180){
ANGLE_STEP = 40
}
var speed = document.getElementById('speed')
speed.innerHTML = ANGLE_STEP
}
function down(){
ANGLE_STEP -= 10
if (ANGLE_STEP < 0){
ANGLE_STEP = 40
}
var speed = document.getElementById('speed')
speed.innerHTML = ANGLE_STEP
}

示例

<!DOCTYPE html>

rotate triangle Please use the browser supporting "canvas".
当前速度:40度/秒

Tips: Please indicate the source and original author when reprinting or quoting this article.