requestAnimFrame无法提供恒定的帧率,但我的物理引擎需要。

5
我使用WebGL和Box2D。 Box2D需要一个恒定的帧速率(时间步长用于其“世界”更新)。
function update(time) {//update of box2d world
     world.Step(
           1/60   // 1 / frame-rate
        ,  3      //velocity iterations
        ,  8       //position iterations
     );

但是我读过,像下面这样定义requestAnimFrame才是正确的方式。
     requestAnimFrame = (function() {
     return window.requestAnimationFrame ||
     window.webkitRequestAnimationFrame ||
     window.mozRequestAnimationFrame ||
     window.oRequestAnimationFrame ||
     window.msRequestAnimationFrame ||
     function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
       window.setTimeout(callback, 1000/60);
     };
})();

requestAnimFrame不能给我一个恒定的帧速率,导致我的Box2D变量不同步。

这个问题有解决方法吗?

[编辑]

当实现John的(Cutch)解决方案时,它看起来像这样:

function interpolate(dt) {
    var t = dt/time_step;
    body_coordinates = (1-t) * body_coordinates + t * next_body_coordinates;
}

var physicsDt = 0;
function tick() {
    var time_now = new Date().getTime();
    var dt = time_now - last_time; //Note that last_time is initialized priorly
    last_time = time_now;
    physicsDt += dt;
    clear_the_screen();
    requestAnimFrame(tick);
    drawEverything();
    if(physicsDt >= time_step) {
        update();
        physicsDt -= time_step;
    }
    interpolate(dt);
}

请注意,我的物理更新函数会确保设置next_attribue。此外,在此之前会调用物理update,以使物理世界超前1帧。 结果 动画相当流畅,除了那些我可以看到一些非常糟糕的跳跃和随机出现的微小运动的时候。
我认为以下问题在解决方案中没有得到解决: ----> 1) dt 可能会变得大于 time_step:这将使 dt/time_step 大于1,从而破坏插值方程。
dt 保持大于 time_step 时,问题会增加。是否有可能克服时间间隔变大于 time_step 的问题?
我的意思是,即使我们让世界领先于渲染一帧,如果时间间隔始终保持大于 time_step,它也不会很快超过那个“领先”帧。

----> 2) 假设dttime_step少1毫秒,那么世界就不会更新那一次。现在进行插值并找到近似位置(比应该在的位置晚1毫秒)。

假设下一次dttime_step没有差异。

现在,考虑到dttime_step相等,将不进行插值。因此,接下来绘制的是世界中的“ahead”帧,对吗?(使用那些方程,其中t = 1)

但准确地说,渲染的世界应该是之前落后1毫秒的那个位置。 我的意思是,它落后于世界帧的1毫秒不应该消失。 但是用t = 1,绘制物理世界帧并忘记了那1毫秒。

我对代码或上述两点有误吗?

请求您澄清这些问题。

[编辑]

我在这里的评论区问了这个网页的作者如何高效地绘制多个形状。

我学到了这种方法: 通过为每个形状保留单独的缓冲区并在初始化时仅调用一次createBufferbindBufferbufferData来减少bufferData调用。

每次刷新屏幕时,我都必须遍历所有形状并在绑定所需形状的缓冲区(使用bindBuffer)后调用enableVertexAttribArrayvertexAttribPointer

我的形状不随时间而改变。 它们只是各种形状(如多边形、圆形、三角形),从开始到结束保持不变。

2个回答

6
你必须将物理模拟的步进时间与渲染垂直同步时间分离。最简单的解决方案是这样做:
frameCallback(dt) {
  physicsDt += dt;
  if (physicsDt > 16) {
    stepPhysics();
    physicsDt -= 16;
  }
  renderWorld();
  requestAnimFrame(frameCallback);
}

这里最大的问题是有时候您会使用过时的物理世界进行渲染,例如,如果physicsDt为15,则不会进行任何模拟更新,但是到那个时间点,您的对象已经移动了几乎整个帧。您可以通过保持物理在渲染之前1帧,并在渲染器中线性插值对象位置来解决此问题。类似于:

var t = dt/16.0;
framePosition = (1-t) * previousFramePositions + (t) * nextFramePositions;

那样,即使您的渲染与物理模拟不同步,您的对象也可以平稳移动。如果您有任何问题,请告诉我。
约翰

我仔细阅读了相关内容,最终按照您的指示进行了设置。请查看问题中的修改。 - batman
是的,dt可能会变得比您的物理时间步长更大,可能的原因是您每帧所做的工作超过了16ms(requestAnimationFrame期望的时间片)。此时,您需要简化模拟,使其能够快速运行,或者丢弃时间(将dt限制在16ms或其他值)。 - Cutch
编辑#2)我认为您正在使用错误的变量进行插值-您想要根据physicsDt而不是dt进行插值。如果您的physicsDt为15ms(比timeStep小1ms),则您的插值将是正确的,您将完成93%的帧,并且下一个tick()调用将模拟1个更多的物理帧,然后您将完成该帧的15ms((15ms + 16ms)%16ms)。 - Cutch
是的,第二次编辑问题已经解决。但是,“throw away time”和“clamp dt at 16 ms”是什么意思?我现在知道简化我的WebGL模拟可以解决问题。但出于某些原因,我似乎找不到比我在另一个编辑中展示的更好的绘制多个形状的方法。 所以,请告诉我你提到的其他替代方法是什么意思。当然,如果“dt”始终被夹在16ms处,那将是很好的,但如果我的模拟占用的时间超过16ms,那可能不可能。对于这么多问题,我很抱歉... - batman
通过“抛弃时间”,我指的是每32毫秒模拟一次物理效果,而不是每16毫秒。这样可以让您使用1个animationFrame进行渲染,1个animationFrame进行物理模拟。 - Cutch
显示剩余5条评论

1

requestAnimFrame并不保证恒定的帧率 - 它的设计是让浏览器只计算它实际绘制的帧。

如果您想/必须计算未绘制的帧,则不应使用该方法。


浏览器在我的情况下可以绘制。它通过WebGL模拟物理效果。 - batman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接