如何在两个游戏状态之间进行插值?


24

创建在两个更新状态之间插值所有对象位置的系统的最佳模式是什么?

更新将始终以相同的频率运行,但是我希望能够以任何FPS渲染。因此,无论每秒帧数是低于还是高于更新频率,渲染都将尽可能平滑。

我想将1帧更新为从当前帧到将来帧的将来插值。这个答案有一个讨论此操作的链接:

半固定或全固定时间步长?

编辑:如何在插值中使用最近和当前速度?例如,仅使用线性插值,它将在位置之间以相同的速度移动。我需要一种方法来对两点之间的位置进行插值,但是要考虑到插值在每个点的速度。这对于诸如粒子效果之类的低速率模拟将很有帮助。


2
滴答是逻辑滴答?那么您的更新fps <渲染fps?
共产党鸭子

我改变了这个词。但是,是的,逻辑logic。而且不行,我想完全从更新中释放渲染,因此游戏可以以120HZ或22.8HZ进行渲染,并且只要用户满足系统要求,更新仍将以相同的速度运行。
AttackingHobo

这可能确实很棘手,因为在渲染时,所有对象的位置都应保持静止(在渲染过程中更改它们可能会导致某些未定义的行为)
Ali1S232 2011年

插值将在两个已经计算的更新帧之间的时间计算状态。这不是关于外推,在最后更新帧之后的一段时间计算状态的问题吗?由于下一次更新还没有被限制。
Maik Semder 2011年

我认为,如果他只有一个线程更新/渲染,就不可能只更新渲染位置。您只需将位置发送到GPU,然后重新更新即可。
zacharmarz

Answers:


22

您要分离更新(逻辑刻度)和绘制(渲染刻度)速率。

您的更新将产生要绘制的世界上所有对象的位置。

在这里,我将介绍两种不同的可能性,一种是您要求的外推法,另一种是插值方法。

1。

外推法是计算下一帧对象的(预测)位置,然后在当前对象位置和下一帧对象的位置之间进行插值的地方。

为此,每个要绘制的对象必须具有关联的velocityposition。为了找到对象在下一帧的位置,我们只需将其添加velocity * draw_timestep到对象的当前位置,即可找到下一帧的预测位置。draw_timestep是自上次渲染刻度(又称上一次绘制调用)以来经过的时间。

如果将其保留在此处,则会发现对象的预测位置与下一帧的实际位置不匹配时会“闪烁”。要消除闪烁,您可以在每个绘制步骤中将预测位置以及lerp存储在先前预测位置和新预测位置之间,并使用自上次更新刻度以来的经过时间作为lerp因子。当快速移动的对象突然更改位置时,这仍然会导致不良行为,您可能需要处理这种特殊情况。本段中所说的所有内容都是您希望使用推断的原因。

2。

插值是我们存储最后两个更新的状态的位置,并根据自上一次更新以来经过的当前时间在它们之间进行插值。在此设置中,每个对象必须具有关联的positionprevious_position。在这种情况下,我们的图形将在当前游戏状态之后的最坏情况下代表一个更新滴答声,而在最佳状态下则代表与当前更新状态完全相同的状态。


在我看来,您可能希望像我所描述的那样进行插值,因为这两种方法都更容易实现,并且在当前更新状态之后抽出一秒(例如1/60秒)的时间就可以了。


编辑:

如果以上内容不足以允许您执行实现,下面是一个示例,介绍了如何执行我所介绍的插值方法。我不会介绍外推法,因为我无法想到您应该偏爱的任何现实情况。

创建可绘制对象时,它将存储需要绘制的属性(即,绘制它所需的状态信息)。

对于此示例,我们将存储位置和旋转。您可能还需要存储其他属性,例如颜色或纹理坐标位置(即,如果纹理滚动)。

为了防止在渲染线程绘制数据时修改数据(即,在渲染线程绘制时更改了一个对象的位置,但尚未更新其他所有对象的位置),我们需要实现某种类型的双缓冲。

一个对象存储它的两个副本previous_state。我将它们放在一个数组中,并将其称为previous_state[0]previous_state[1]。同样,它需要两个副本current_state

为了跟踪使用哪个双缓冲区副本,我们存储了一个变量state_index,该变量可用于更新线程和绘制线程。

更新线程首先使用对象自己的数据(所需的任何数据结构)计算对象的所有属性。然后,它拷贝current_state[state_index]previous_state[state_index],并复制新的数据相关的图纸,positionrotationcurrent_state[state_index]。然后执行state_index = 1 - state_index,以翻转当前使用的double缓冲区副本。

上段中的所有操作都必须在锁定状态下进行current_state。更新和绘制线程都将取消此锁定。仅在复制状态信息的过程中才取出锁定,这是很快的。

然后在渲染线程中,对位置和旋转进行线性插值,如下所示:

current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)

elapsed自上次更新刻度以来,在渲染线程中经过的时间是哪里,并且update_tick_length是固定的更新速率每刻度所花费的时间(例如,在20FPS更新时update_tick_length = 0.05)。

如果您不知道Lerp上面的函数是什么,那么请查看Wikipedia上有关该主题的文章:线性插值。但是,如果您不知道残差是什么,那么您可能还没有准备好使用插值图实现解耦的更新/图。


1
+1必须对方向/旋转以及随时间变化的所有其他状态(例如,粒子系统中的材质动画等
相同的操作。– Maik Semder 2011年

1
Maik说得好,我只是以职位为例。如果要使用推断,则需要存储要推断的任何属性的“速度”(即,该属性随时间的变化率)。最后,我真的无法想到外推比内插更好的情况,我之所以将其包括在内是因为提问者的问题要求这样做。我使用插值。使用插值时,我们需要存储要插值的任何属性的当前和先前更新结果,就像您所说的那样。
Olhovsky 2011年

这是问题的重述,以及内插和外推之间的区别;这不是答案。

1
在我的示例中,我在状态中存储了位置和旋转。您也可以只存储状态中的速度(或速度)。然后,您以完全相同的方式在速度之间切换(Lerp(previous_speed, current_speed, elapsed/update_tick_length))。您可以使用要存储在该州的任何号码来执行此操作。给定一个lerp因子,绑扎只是为您提供两个值之间的一个值。
Olhovsky 2011年

1
对于角移动的插值,建议使用slerp而不是lerp。最简单的方法是存储两个州的四元数,并存储它们之间的状态。否则,相同的规则适用于角速度和角加速度。您有骨骼动画的测试用例吗?
Maik Semder 2011年

-2

这个问题要求您对开始和结束的定义有所不同。刚开始的程序员经常想到每帧位置的变化,这是一开始的好方法。为了我的回答,让我们考虑一个一维的答案。

假设您在位置x处有一只猴子。现在,您还有一个“ addX”,您可以根据键盘或其他控件将其添加到猴子每帧的位置。只要您有保证的帧速率,这将起作用。假设您的x是100,而addX是10。在10帧之后,您的x + = addX应该累加到200。

现在,当帧速率可变时,您应该考虑速度和加速度,而不是addX。我将向您介绍所有这些算法,但是它非常简单。我们想知道的是每毫秒(1/1000秒)要行驶多远

如果您以30 FPS的速度拍摄,那么velX应该是1/3秒(距离上一个示例以30 FPS的距离有10帧),并且您知道您想在这段时间内旅行100'x',因此将velX设置为100距离/ 10 FPS或每帧10距离。以毫秒为单位,得出每3.3毫秒1距离x或每毫秒0.3'x'。

现在,每次更新时,您所需要做的就是弄清楚时间的流逝。无论是经过33毫秒(1/30秒)还是任何时间,您只需将距离0.3乘以经过的毫秒数即可。这意味着您需要一个计时器,该计时器可以为您提供毫秒(毫秒)的精度,但是大多数计时器都可以做到这一点。只需执行以下操作:

var beginTime = getTimeInMillisecond()

...稍后...

var time = getTimeInMillisecond()

var elapsedTime = time-beginTime

beginTime =时间

...现在使用此elapsedTime来计算您的所有距离。


1
他没有可变的更新率。他具有固定的更新率。老实说,我真的不知道您要在这里提出什么观点:/
Olhovsky 2011年

1
??? -1。这就是全部,我有保证的更新率,但是渲染率是可变的,并且我希望它平滑而不出现卡顿现象。
AttackingHobo

可变更新率不适用于网络游戏,竞技游戏,重播系统或任何其他依赖确定性游戏的内容。
AttackingHobo

1
固定更新还可以轻松集成伪摩擦。例如,如果您想将每帧的速度乘以0.9,那么如何计算出快帧或慢帧的倍数呢?有时有时更喜欢固定更新,因为几乎所有物理模拟都使用固定更新率。
Olhovsky

2
如果我使用可变的帧速率,并设置了一个复杂的初始状态,其中许多对象相互反射,则无法保证它会完全相同。实际上,每次模拟很可能会稍有不同,在开始时会有很小的差异,在很短的时间内会在每次模拟运行之间复合为完全不同的状态。
AttackingHobo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.