帧率独立 acceleration/decceleration?

Framerate independent acceleration/decceleration?

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

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

时间步的值是当前帧的百分比。所以全帧时间步长为 1,半帧时间步长为 .5,四分之一帧时间步长为 .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

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

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

速度是位置随时间的变化。你已经在你的等式中正确计算了。

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;

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

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

对于视频游戏和 UI 动画,按帧速率顺序的时间步长,例如每秒 10-100 步,通常足以使误差足够小,不会对动画的外观或游戏的可玩性。

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

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

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

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

[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秒的模拟)

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

一旦允许变化率本身发生变化(即一旦速度可以由于重力或摩擦等加速度而发生变化),则违反了该假设。 slower/longer 时间步上的代码 运行 将假定速度在整个持续时间内只有一个值,而在 faster/shorter 时间步上运行的代码将在同一时间看到多个不同的值间隔,自然得出不同的结果。

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。我还包括了一个根本不使用累积增量的分析解决方案,以表明不仅具有不同时间步长的模拟彼此一致,而且它们也与基本事实一致(直到舍入误差)。