目录

WebGLFundamentals.org

Fix, Fork, Contribute

WebGL 动画

此文上接WebGL系列文章,第一篇是基础概念。 前一篇是三维相机,如果没读建议从那开始。

如何使用WebGL实现动画?

事实上这个问题和WebGL并不相关,如果你想实现动画只需要利用JavaScript 随着时间改变一些值然后绘制。

我们可以看看之前使用的动画的例子。

*var fieldOfViewRadians = degToRad(60);
*var rotationSpeed = 1.2;

*requestAnimationFrame(drawScene);

// 绘制场景
function drawScene() {
*  // 每一帧旋转一点
*  rotation[1] += rotationSpeed / 60.0;

  ...
*  // 下一帧继续调用 drawScene 
*  requestAnimationFrame(drawScene);
}

这是结果

这里还有一些小细节,上方的代码中有rotationSpeed / 60.0, 我们除以 60.0 是假设浏览器一秒钟调用 60 次 requestAnimationFrame。

这并不是一个有效的假设,也许用户使用的是一个低效的设备,比如旧的智能手机。 或者用户在后台运行了一个复杂的程序。有多种原因可能导致浏览器不是60帧每秒。 也许在 2020 年所有的设备每秒 240 帧,也许用户是一个游戏玩家,在CRT显示器上每秒 90 帧。

你可以在这个例子中发现问题

在上方的例子中我们想让所有的 'F' 以相同的速度转动。 中间的 'F' 全速运行,并且帧率独立。左边和右边的模拟的是当前设备 1/8 的速率, 左边的不是帧率独立,右边的是帧率独立。

注意到由于左边的使用的计数,所以帧率变慢不能跟上正常速度。 右边的尽管以 1/8 的帧率运行,它还是能和中间的保持相同的速度。

让帧率独立的方法是计算两帧之间的时间,用这个时间计算当前帧的动画量。

首先需要得到时间,幸好页面加载调用 requestAnimationFrame 时会传入时间。

我觉得如果时间的单位是秒会简单一些,由于 requestAnimationFrame 传递给我们的时间是毫秒 (千分之一秒),我们需要将它乘以 0.001 得到秒。

所以就可以像这样计算出时间差

*var then = 0;

requestAnimationFrame(drawScene);

// 绘制场景
*function drawScene(now) {
*  // 转换时间为秒
*  now *= 0.001;
*  // 减去上一次的时间得到时间差
*  var deltaTime = now - then;
*  // 记住这次时间
*  then = now;

   ...

一旦有了 deltaTime 就可以决定每秒转多少个单位,例子中是 1.2 意思就是每秒转动 1.2 弧度。 大约是 1/5 圈或者说 5 秒可以转一整圈,不管帧率是多少。

*    rotation[1] += rotationSpeed * deltaTime;

这是这个版本

除非你的机器比较慢,不然可能看不出和该页第一个的区别, 但是如果不使动画帧率独立,不同的用户可能会得到预期之外的结果。

接下来讲如何使用纹理。

不要使用 setInterval 或 setTimeout!

如果你之前使用JavaScript实现动画可能会使用setInterval 或 setTimeout 调用绘制。

使用 setInterval 或 setTimeout 的问题是会出现双重动画。 首先 setInterval 或 setTimeout 和浏览器的显示无关, 它们和浏览器绘制每一帧不同步,所以就不能和用户的设备同步。如果使用 setInterval 或 setTimeout 并且假设 60 帧每秒, 然后用户的设备实际帧率可能是其他数值,就会和设备不同步。

另一个问题是浏览器不知道你为什么使用setInterval 或 setTimeout。 例如,即使你的页面是不可见的,例如不是当前打开的标签,浏览器还是会运行你的代码。 也许你会使用 setInterval 或 setTimeout 查看邮箱或 tweeter, 浏览器并不知道你的目的是什么。每秒都检查新邮件和 tweeter 没什么问题,但每秒钟都使用WebGL绘制 1000 个物体就有问题了。你将对用户不可见的标签页进行绘制。

requestAnimationFrame 解决了这些问题,他只会在正确的时间调用, 保持动画和屏幕帧率相同,并且只会在可见时调用。

有疑问? 在stackoverflow上提问.
Issue/Bug? 在GitHub上提issue.
使用 <pre><code> 代码 </code></pre> 的格式编写代码块
comments powered by Disqus