在WebGL中创建动画
前言
在搭建WebGL开发环境中介绍了如何开始使用webgl进行绘制。
本篇文章介绍如何在WebGL中创建动画
动画的定义
动画是一种通过快速显示一系列图像(或帧)模拟运动的技术。
动画的分类
网页上的动画基本分为三类:
- 声明式动画,如CSS动画和SVG动画,这类动画不需要js的介入,用户只需要说明元素如何运动,不需要定义每帧的内容
- 基于脚本的动画,这种动画每一帧的执行都是靠js来控制的,这类动画包括
- 通过更新DOM元素的样式对象
- 在HTML5 2D画布上绘制对象
- 通过WebGL来绘制对象,这也是本篇文章的重点
- 基于文件的动画,这类动画的每帧内容包含在文件中,比如GIF动画,比如Flash动画。在浏览器中使用这种动画可能需要相应的插件。
WebGL使用动画
抛开技术不谈,我们先梳理一下使用webgl技术怎么实现一个动画,比如最简单的,让一个点移动,过程如下:
- 清空画布
- 上传顶点的位置
- 绘制顶点,到这一步,顶点并没有移动
- 我们需要经过一帧的时间,然后
- 再次清空画布
- 修改顶点的位置并上传,
- 重新绘制顶点
只要我们每一帧的时间足够短,我们就能看到一个点在画布上运动。
setInterval()
说明
setInterval()方法重复调用一个函数或执行一个代码片段,在每次调用之间具有固定的时间间隔。
定义
var intervalID = setInterval(func, [delay, arg1, arg2, ...]);
参数
- func: 要重复调用的函数,每经过指定 delay
毫秒
后执行一次。第一次调用发生在 delay 毫秒之后。 - delay: 是每次延迟的毫秒数(一秒等于 1000 毫秒),函数的每次调用会在该延迟之后发生。
如果未指定,则其默认值为 0,也就是每个事件循环都会执行一次
- arg: 可选参数,当计时结束的时候,将被传递给 func 函数的附加参数。
返回值
它返回一个 interval ID,该 ID 唯一地标识时间间隔,因此你可以稍后通过调用 clearInterval() 来移除定时器。
setTimeout()
说明
setTimeout() 方法设置一个定时器,一旦定时器到期,就会执行一个函数或指定的代码片段。
定义
setTimeout(func, delay, arg1, arg2, /* … ,*/ argN)
参数
- func: 经过指定 delay
毫秒
后执行的函数。 - delay: 定时器在执行指定的函数或代码之前应该等待的时间,单位是毫秒。
如果省略该参数,则使用值 0,意味着“立即”执行
,或者更准确地说,在下一个事件循环执行。 - arg:附加参数,一旦定时器到期,它们会作为参数传递给 func指定的函数。
返回值
返回值 timeoutID 是一个正整数,表示由 setTimeout() 调用创建的定时器的编号。这个值可以传递给 clearTimeout() 来取消该定时器。
requestAnimationFrame
虽然Web开发人员很久以前就可以用setInterval和setTimeout函数创建动画,但是最近人们推荐另一种实现基于脚本的动画的方式。
requestAnimationFrame
,与setInterval和setTimeout函数一样,后者也是HTML DOM窗口对象的一部分
requestAnimationFrame是一个全局函数,是浏览器用于定时循环
操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘,让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
关于requestAnimationFrame,需要注意一下几点:
- requestAnimationFrame() 是一次性的,也就是说你使用接口后,浏览器会在下一次刷新时调用你传进去的回调函数,但是只有一次有效,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 requestAnimationFrame()
- requestAnimationFrame的刷新频率是通过浏览器来控制的,而浏览器的刷新频率就是最佳刷新频率。
- 浏览器也可以减速更新或暂停更新在某个不可见选项卡中正在运行的动画。这样,用这个方法不仅可以改善动画的性能,还可以节省电能,后者对于移动设备尤为重要。电池寿命通常是移动设备的一个重要因素。
定义
requestAnimationFrame(callback)
参数
callback:当你的动画需要更新时,为下一次重绘所调用的函数。该回调函数会传入一个参数,表示 requestAnimationFrame() 开始执行回调函数的时间戳。
注意:在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位为毫秒,最小精度为 1ms
返回值
一个long整数,请求 ID,是回调列表中唯一的标识。是个非零值,没有别的意义。你可以传这个值给window.cancelAnimationFrame()
以取消回调函数请求。
下面是一个使用requestAnimationFrame设置动画的例子
// 所有动画回调的集合
var animCallbacks=new Array();
/**
* 设置刷新
* @param {*} fps 刷新率
* @param {*} updateCallback 回调函数,处理每帧的逻辑
*/
function setUpdate(fps,updateCallback)
{
updateCallback.interval = 1000/fps;
updateCallback.lastTime = undefined;
animCallbacks.push(updateCallback);
if(animCallbacks.length==1)
{
tick();
}
}
/**
* 时钟,用于确定是否需要刷新
*/
function tick(timeStamp)
{
requestAnimationFrame(tick.bind(this));
animCallbacks.forEach(callback => {
if(!callback)
{
return;
}
if(callback.lastTime == undefined)
{
callback.lastTime = timeStamp;
}
if(timeStamp && timeStamp - callback.lastTime>=callback.interval)
{
callback.lastTime = timeStamp;
callback();
}});
}
完整代码已上传gitlab