本文隶属于WebGL系列教程,首篇基础教程从核心概念讲起,前文则探讨了3D相机相关内容。若尚未阅读,请先参阅。
如何在WebGL中让物体动起来?
本质上,这并非WebGL特有机制——任何JavaScript动画都需要随时间改变状态并重绘。
我们选取一个前文示例进行动画改造。
*var fieldOfViewRadians = degToRad(60);
*var rotationSpeed = 1.2;
*requestAnimationFrame(drawScene);
// Draw the scene.
function drawScene() {
* // Every frame increase the rotation a little.
* rotation[1] += rotationSpeed / 60.0;
...
* // Call drawScene again next frame
* requestAnimationFrame(drawScene);
}
效果如下:
但存在一个潜在问题,代码中的 rotationSpeed / 60.0
是基于浏览器每秒60次响应 requestAnimationFrame
的假设,
这种帧率假设并不可靠。
该假设实际上并不成立。用户可能使用旧款智能手机等低性能设备,或后台运行重负载程序。 浏览器帧率受多种因素影响,未必保持60FPS——例如2020年后硬件或支持240FPS,或电竞玩家使用90Hz刷新率的CRT显示器。
可通过此示例观察该问题:
上例中,我们想让所有’F’保持相同的转速。中间的F全速运行,帧率无关。 左右两侧的F,模拟浏览器只有1/8性能运行的情况。在左侧的F是帧率无关的。右边的F,帧率相关。
可见:左侧F因未考虑帧率下降而无法同步,右侧F即使以1/8帧率运行仍与全速运行的中央F保持同步。
实现帧率无关动画的核心方法是:计算帧间时间差,并据此确定当前帧的动画状态增量。
先需要获取时间值。幸运的是 requestAnimationFrame
在回调时会自动传入页面加载后的时间戳。
为简化计算,转换为以秒为单位最简单。由于 requestAnimationFrame
提供的时间单位为毫秒(1/1000秒),需乘以0.001转换为秒。
由此,可按下面的方式计算时间增量:
*var then = 0;
requestAnimationFrame(drawScene);
// Draw the scene.
*function drawScene(now) {
* // Convert the time to seconds
* now *= 0.001;
* // Subtract the previous time from the current time
* var deltaTime = now - then;
* // Remember the current time for the next frame.
* then = now;
...
一旦获得以秒为单位的deltaTime
,我们所有的计算都可以基于"每秒单位量"进行。在本例中:
rotationSpeed
设为1.2,表示每秒旋转1.2弧度。约等于1/5圆周,完整旋转一周约需5秒,且该速率与帧率无关。
* rotation[1] += rotationSpeed * deltaTime;
以下是实现效果:
除非在低性能设备上运行,否则您可能难以察觉与本页顶部示例的差异。但若不实现帧率无关的动画,部分用户获得的体验将与您的设计预期大相径庭。s
接下来学习如何应用纹理。