【问题标题】:Framerate independent acceleration/decceleration?帧率独立加速/减速?
【发布时间】:2017-05-14 03:57:00
【问题描述】:

我正在用 C++ 编写粒子模拟器。

我通过在每个时间步将它们的速度添加到它们的位置来移动粒子。

时间步长的值是当前帧的百分比。所以全帧时间步长是1,半帧时间步长是0.5,四分之一帧时间步长是0.25,等等。总的模拟步数是frameCount/timeStep……所以时间步越小,总的数量就越大模拟步骤。

保持跨时间步长的基本运动相同非常简单。等式是:

position = position + velocity * timeStep; //10 full frames later, end position is always the same

但是,一旦我尝试随着时间的推移改变速度,对于我目前对数学的理解来说,它变得太复杂了。例如,如果我这样做:

velocity = velocity * .95f;
position = position + velocity * timeStep; //10 full frames later, end position dependent on time step

不同时间步长的结果不再相同。我知道这是因为如果我通过减少时间步来增加计算的总步数,我也会将速度降低很多倍,这将对粒子的最终位置产生很大影响。

如何随着时间的推移修改速度,以便在不同的时间步长上获得相同的结果?

【问题讨论】:

    标签: c++ calculus


    【解决方案1】:

    I noticed users are finding this Q&A and being misled,所以我想在这里发帖指出接受的答案不正确,并且没有像它声称的那样给出与帧率无关的结果。

    我们可以通过简单的电子表格计算来证明这一点。设置三个具有不同时间步长的时间序列,并使用该答案中的公式:

    [Velocity Cell] = [Previous Velocity Cell]
                    + (Accel - Frict * [Previous Velocity Cell] * [Time Step]) * [Time Step]
    
    [Position Cell] = [Previous Position Cell] + [Velocity Cell] * [Time Step]
    

    这三组结果在很短的时间后就会明显不同(这里我想象的是 1.6 秒的模拟)

    这是意料之中的,因为乘以时间步长只能正确解释 线性 变化。它隐含地假设变化率在我们模拟的区间内保持不变,因此该区间内的总变化就是当前速率乘以区间持续时间。

    只要允许变化率本身发生变化(即,一旦速度由于重力或摩擦等加速度而发生变化),那么这个假设就被违反了。在较慢/较长时间步长上运行的代码将假定速度在整个持续时间内只有一个值,而在较快/较短时间步长上运行的代码将在同一时间间隔内看到多个不同的值,并且自然会到达不同的值结果。

    I explore this problem in more detail in this answer here,展示了如何推导计算模拟区间内变化率变化的方程,但这里是 TL;DR:

    如果您希望以恒定加速度与帧速率无关的集成,请使用:

    position += velocity * timeStep + 0.5 * acceleration * timeStep * timeStep;
    velocity += acceleration * timeStep;
    

    如果您想要与 乘法拖动 实现与帧速率无关的集成,请使用:

    newVelocity = velocity * pow(fractionRemainingAfterOneSecond, timeStep);
    
    position += (newVelocity - velocity) / naturalLog(fractionRemainingAfterOneSecond);
    
    velocity = newVelocity;
    

    如果您想要与加速和拖动无关的帧速率集成......您无法获得它(抱歉)。我们没有积分的闭式解

    即使我在上面给出的两个所谓的与帧速率无关的解决方案也是如此,如果你有无限精度的实数。在实际代码中,每一步都会出现舍入误差,并且以不同的时间步长运行相同的模拟会导致舍入误差在不同的地方堆积。但它们将比公认答案中提供的更强大。


    为了完整起见,这里比较显示了使用上述与帧速率无关的公式模拟的轨迹,以 60 FPS 和 10 FPS 步进。我还提供了一个根本不使用累积增量的分析解决方案,以表明具有不同时间步长的模拟不仅彼此一致,而且还与基本事实一致(直至舍入误差)。

    【讨论】:

    • (至少对于我的应用程序)接受的答案可以完成工作。我已经通过打印每帧的增量位置进行了尝试。以客户端 fps 一半的速度运行的服务器确实使客户端的增量位置加倍。但是,根据您提供的公式(第一个),它并不是客户增量位置的两倍。
    • 这个答案的主旨是,当对象加速时,客户端的增量位置的两倍不正确。当对象以恒定速度移动时,当间隔加倍时,我们预计增量会加倍。如果物体正在减速,那么超过两倍的时间间隔它应该行进的距离应该略小于两倍的距离(因为它在下半场移动得更慢)。如果物体正在加速,它应该行进两倍以上的距离(因为它在下半场移动得更快)。你需要比较的是绝对位置。
    【解决方案2】:

    速度是位置随时间的变化。您已经在方程式中正确计算了这一点。

    position = position + velocity * timeStep;
    

    加速度是速度随时间的变化。因此,您只需使用相同的方程式,但相应地修改变量。也就是说,将位置更改为速度,将速度更改为加速度。时间步长保持不变。

    velocity = velocity + acceleration * timeStep;
    

    如果你想模拟摩擦,那么你所做的就是将速度乘以某个恒定的摩擦值和时间步长,然后从加速度中减去它。但是这个值应该只用于帧,而不是存储在实际的加速度值中。

    float temp_accel = acceleration - friction * velocity * timeStep;
    

    然后根据 temp_accel 修改你的速度。

    velocity = velocity + temp_accel * timeStep;
    

    如果你的加速度为零,那么你可以把它排除在等式之外:

    float temp_accel = -friction * velocity * timeStep;
    

    【讨论】:

    • 嗯,那么我如何根据我的初始(减)加速度公式(速度 *= .95)计算出你的函数中的加速度值应该是多少?
    • @Tyson:那不是减速公式。因此,没有正确的方法将其转换为减速。我会建议你玩弄数字,直到你得到一个看起来和感觉正确的值。
    • 不是吗?它描述了速度随时间的变化,即下降。我想我希望有一个数学解决方案,而不是“摇晃一切,直到它起作用”的解决方案。
    • @Tyson:如果不知道你想要什么效果,我无法知道正确的数学解决方案。
    • 好吧,在这种情况下,我试图给我的粒子增加摩擦力,以随着时间的推移减慢它们的速度。现在我只是在模拟的每一步将它们的速度乘以 x(在本例中为 0.95)。它提供了减慢粒子速度的预期效果,但由于前面列出的原因,它与帧速率无关。我想知道如何以独立于帧速率的方式减慢它们的速度。
    【解决方案3】:

    公认的答案很好,但我想强调的是,您永远无法完全独立于模拟的步长。

    运动方程(在某个时间点为我们提供位置和速度)是积分。模拟通过小步求和来近似积分。但是因为这些步骤是有限的而不是无穷小的,所以总是有错误。步数越小,误差越小。

    对于视频游戏和 UI 动画,帧速率数量级的时间步长(例如每秒 10-100 步)通常足以使误差保持足够小,从而不会对动画的外观产生不利影响或游戏的可玩性。

    但是,如果您以每秒 10 步和 100 步的速度运行相同的模拟,您可能会得到不同的结果,因为后者比前者更接近。

    减小步数也会增加计算量。有时,您没有足够的能力来使用足够小的步骤来将错误控制在合理范围内。对于这些情况,有数值积分器,它比重复求和提供更好的近似值。可能最著名的数值积分器是Runge-Kutta (RK4)。如果您发现您必须将timeStep 设置得太小,请尝试使用 RK4。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多