创建在两个更新状态之间插值所有对象位置的系统的最佳模式是什么?
更新将始终以相同的频率运行,但是我希望能够以任何FPS渲染。因此,无论每秒帧数是低于还是高于更新频率,渲染都将尽可能平滑。
我想将1帧更新为从当前帧到将来帧的将来插值。这个答案有一个讨论此操作的链接:
编辑:如何在插值中使用最近和当前速度?例如,仅使用线性插值,它将在位置之间以相同的速度移动。我需要一种方法来对两点之间的位置进行插值,但是要考虑到插值在每个点的速度。这对于诸如粒子效果之类的低速率模拟将很有帮助。
创建在两个更新状态之间插值所有对象位置的系统的最佳模式是什么?
更新将始终以相同的频率运行,但是我希望能够以任何FPS渲染。因此,无论每秒帧数是低于还是高于更新频率,渲染都将尽可能平滑。
我想将1帧更新为从当前帧到将来帧的将来插值。这个答案有一个讨论此操作的链接:
编辑:如何在插值中使用最近和当前速度?例如,仅使用线性插值,它将在位置之间以相同的速度移动。我需要一种方法来对两点之间的位置进行插值,但是要考虑到插值在每个点的速度。这对于诸如粒子效果之类的低速率模拟将很有帮助。
Answers:
您要分离更新(逻辑刻度)和绘制(渲染刻度)速率。
您的更新将产生要绘制的世界上所有对象的位置。
在这里,我将介绍两种不同的可能性,一种是您要求的外推法,另一种是插值方法。
1。
外推法是计算下一帧对象的(预测)位置,然后在当前对象位置和下一帧对象的位置之间进行插值的地方。
为此,每个要绘制的对象必须具有关联的velocity
和position
。为了找到对象在下一帧的位置,我们只需将其添加velocity * draw_timestep
到对象的当前位置,即可找到下一帧的预测位置。draw_timestep
是自上次渲染刻度(又称上一次绘制调用)以来经过的时间。
如果将其保留在此处,则会发现对象的预测位置与下一帧的实际位置不匹配时会“闪烁”。要消除闪烁,您可以在每个绘制步骤中将预测位置以及lerp存储在先前预测位置和新预测位置之间,并使用自上次更新刻度以来的经过时间作为lerp因子。当快速移动的对象突然更改位置时,这仍然会导致不良行为,您可能需要处理这种特殊情况。本段中所说的所有内容都是您不希望使用推断的原因。
2。
插值是我们存储最后两个更新的状态的位置,并根据自上一次更新以来经过的当前时间在它们之间进行插值。在此设置中,每个对象必须具有关联的position
和previous_position
。在这种情况下,我们的图形将在当前游戏状态之后的最坏情况下代表一个更新滴答声,而在最佳状态下则代表与当前更新状态完全相同的状态。
在我看来,您可能希望像我所描述的那样进行插值,因为这两种方法都更容易实现,并且在当前更新状态之后抽出一秒(例如1/60秒)的时间就可以了。
编辑:
如果以上内容不足以允许您执行实现,下面是一个示例,介绍了如何执行我所介绍的插值方法。我不会介绍外推法,因为我无法想到您应该偏爱的任何现实情况。
创建可绘制对象时,它将存储需要绘制的属性(即,绘制它所需的状态信息)。
对于此示例,我们将存储位置和旋转。您可能还需要存储其他属性,例如颜色或纹理坐标位置(即,如果纹理滚动)。
为了防止在渲染线程绘制数据时修改数据(即,在渲染线程绘制时更改了一个对象的位置,但尚未更新其他所有对象的位置),我们需要实现某种类型的双缓冲。
一个对象存储它的两个副本previous_state
。我将它们放在一个数组中,并将其称为previous_state[0]
和previous_state[1]
。同样,它需要两个副本current_state
。
为了跟踪使用哪个双缓冲区副本,我们存储了一个变量state_index
,该变量可用于更新线程和绘制线程。
更新线程首先使用对象自己的数据(所需的任何数据结构)计算对象的所有属性。然后,它拷贝current_state[state_index]
到previous_state[state_index]
,并复制新的数据相关的图纸,position
和rotation
成current_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上有关该主题的文章:线性插值。但是,如果您不知道残差是什么,那么您可能还没有准备好使用插值图实现解耦的更新/图。
Lerp(previous_speed, current_speed, elapsed/update_tick_length)
)。您可以使用要存储在该州的任何号码来执行此操作。给定一个lerp因子,绑扎只是为您提供两个值之间的一个值。
这个问题要求您对开始和结束的定义有所不同。刚开始的程序员经常想到每帧位置的变化,这是一开始的好方法。为了我的回答,让我们考虑一个一维的答案。
假设您在位置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来计算您的所有距离。