目标:绘制一个不停旋转的三角形
基础概念
- 为了一个三角形转动起来,你需要做的是:不断擦除和重绘三角形,并且在每次重绘时轻微的改变其角度。
- requestAnimationFrame(func) 请求浏览器在将来某时刻回调函数func以完成重绘。我们应当在回调函数最后再次发起该请求。
参数:func 指定将来某时刻调用的函数。函数会接收到一个time参数,用来表明此次调用的时间戳
返回值:Request id
传统习惯上来说,如果想js重复执行某个特定任务会用setInterval()函数,但是setInterval函数有个特点:在浏览器打开多标签页时,不论用setInterval的标签页是否被激活,其中的setInterval函数都会反复调用其回调函数,如果标签页比较多,就会增加浏览器的负荷,所以后来浏览器又引入了requestAnimation()方法,该方法只有当标签页处于激活状态时才会生效。requestAnimation()是新引入的方法,还没有实现标准化,不过Google提供的webgl-util.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(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}
`
var ANGLE_STEP = 45.0; // 三角形每秒旋转的角度
var Tx = 0.5;
var g_last = Date.now();
function main () {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
gl.clearColor(0.0,0.0,0.0,1.0);
if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
return;
}
// 三角形初始位置
var n = initVertexBuffers(gl);
// 三角形初始角度
var currentAngle = 0.0;
// 变换矩阵uniform变量的存储地址
var u_ModelMatrix = gl.getUniformLocation(gl.program,'u_ModelMatrix');
// 变换矩阵
var modelMatrix = new Matrix4();
// 反复调用绘制函数的机制
var tick = function () {
// 获取上一帧动画后的当前angle
currentAngle = animate(currentAngle);
// 绘制三角形
draw(gl,n,currentAngle,u_ModelMatrix,modelMatrix);
// 请求浏览器再次调用tick
requestAnimationFrame(tick);
}
tick();
}
main();
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 the 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');
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(a_Position);
return n;
}
function animate (angle) {
// 计算距离上次调用经过了多长时间
var now = Date.now();
var elapsed = now - g_last; //毫秒
g_last = now;
// requestAnimationFrame() 调用tick的时间间隔不是固定的
// 根据本次调用和上次调用之间的时间间隔来决定这一帧的旋转角度比上一帧大多少,除以1000的目的将秒转成毫秒
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
// 保证newAngle始终小于360度
return newAngle %= 360;
}
function draw (gl,n,currentAngle,u_ModelMatrix,modelMatrix) {
// 将变换矩阵的值重新设置
modelMatrix.setRotate(currentAngle,0,0,1);
gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
// 绘制三角形
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,n);
}
代码中我们没有在draw()函数内部创建模型矩阵modelMatrix(而是在函数外部创建)是因为,如果我们那样做,那么每次调用draw()函数时都会新建一个Matrix4对象,这会降低性能。而在外部创建并传入draw函数,在每次调用draw()函数时,只需要调用其set前缀的方法重新计算,而不需要再新建Matrix4对象
关于requestAnimationFrame(),你无法指定重复调用的间隔:函数func会在浏览器需要网页的某个元素(第2个参数)重绘时被调用。此外还要注意,在浏览器成功(找到了适当的时机)的调用了一次func后,想要再次调用它,就必须再次发起请求,因为前一次请求已经结束(也就是说,requestAnimationFrame更像setTimeout而不是setInterval,不会因为你发起一次请求,就不停的循环调用func),如果你想取消请求,需要使用cancelAnimationFrame()