2つのゲーム状態を補間する方法は?


24

すべてのオブジェクトの位置が2つの更新状態の間で補間されるシステムを作成するための最良のパターンは何ですか?

更新は常に同じ頻度で実行されますが、任意のFPSでレンダリングできるようにしたいです。そのため、更新頻度よりも低いか高いかに関係なく、1秒あたりのフレーム数に関係なく、レンダリングは可能な限りスムーズになります。

現在のフレームから将来のフレームまで、1フレームを将来の補間に更新します。この回答には、これを行うことについて説明するリンクがあります。

半固定または完全固定のタイムステップ?

編集:補間で最後と現在の速度をどのように使用できますか?たとえば、線形補間だけでは、位置間を同じ速度で移動します。2つのポイント間の位置を補間する方法が必要ですが、補間の各ポイントでの速度を考慮します。パーティクル効果のような低レートのシミュレーションに役立ちます。


2
ティックはロジックティックですか?あなたの更新fps <レンダリングfps?
共産主義のダック

用語を変更しました。しかし、はい、ロジックはカチカチです。いいえ、レンダリングから更新を完全に解放したいので、ユーザーがシステム要件を満たしていれば、ゲームは120HZまたは22.8HZでレンダリングでき、更新は同じ速度で実行されます。
AttackingHobo

すべてのオブジェクトの位置をレンダリング中に静止したままにする必要があるため、これは非常に難しい場合があります(レンダリングプロセス中にそれらを変更すると、いくつかの未定義の動作が発生する場合があります)
-Ali1S232

補間は、すでに計算された2つの更新フレーム間の時間の状態を計算します。これは、最後の更新フレームのの時間の状態を計算する外挿に関する質問ではありませんか?次の更新はまだカプセル化されていません。
マイクセンダー

更新/レンダリングが1つのスレッドしかない場合、レンダリング位置だけを再更新することはできないと思います。位置をGPUに送信してから再更新するだけです。
-zacharmarz

回答:


22

更新(論理ティック)レートと描画(レンダリングティック)レートを分離する必要があります。

更新により、描画されるワールド内のすべてのオブジェクトの位置が生成されます。

ここでは、2つの異なる可能性、要求されたもの、外挿、および別の方法である内挿について説明します。

1。

外挿では、次のフレームでオブジェクトの(予測)位置を計算し、現在のオブジェクトの位置と次のフレームでのオブジェクトの位置の間を補間します。

これを行うには、描画される各オブジェクトにとが関連付けられている必要がvelocityありpositionます。オブジェクトが次のフレームにある位置を見つけるにはvelocity * draw_timestep、オブジェクトの現在位置に単純に追加して、次のフレームの予測位置を見つけます。draw_timestepは、前のレンダリングティック(前の描画呼び出し)から経過した時間です。

このままにしておくと、予測された位置が次のフレームの実際の位置と一致しなかったときにオブジェクトが「ちらつき」ます。ちらつきを削除するには、予測位置、および保存することができますlerp lerp因子として前回の更新ティックからの経過時間を使用して、以前に予測位置と各ドロー・ステップで新しい予測位置の間を。これにより、高速で移動するオブジェクトが突然場所を変更した場合の動作が低下し、その特別なケースを処理することができます。この段落で述べたすべてが、外挿を使用したくない理由です。

2。

内挿とは、最後の2つの更新の状態を保存し、最後の前の更新から経過した現在の時間に基づいてそれらの間を補間する場所です。この設定では、各オブジェクトにとが関連付けられている必要がpositionありprevious_positionます。この場合、私たちの図面は、最悪の場合でも現在のゲーム状態の背後にある1つの更新ティックを表し、最高で現在の更新ティックとまったく同じ状態になります。


私の意見では、実装した方が簡単であるため、説明したとおりに補間が必要になるでしょう。また、現在の更新された状態のほんのわずかな秒(たとえば1/60秒)を描画しても問題ありません。


編集:

上記では実装を実行するのに十分ではない場合、ここで説明した補間方法を行う方法の例を示します。外挿については取り上げません。なぜなら、あなたがそれを好むべき現実世界のシナリオは考えられないからです。

描画可能なオブジェクトを作成すると、描画に必要なプロパティ(つまり、描画に必要な状態情報)が保存されます。

この例では、位置と回転を保存します。また、色やテクスチャ座標位置などの他のプロパティを保存することもできます(つまり、テクスチャがスクロールする場合)。

レンダリングスレッドの描画中にデータが変更されないようにするには(つまり、レンダリングスレッドの描画中に1つのオブジェクトの位置が変更されますが、他のすべてはまだ更新されていません)、何らかのタイプのダブルバッファリングを実装する必要があります。

オブジェクトにはの2つのコピーが保存されますprevious_state。私は、配列に入れとしてそれらを参照しますprevious_state[0]previous_state[1]。同様に2つのコピーが必要current_stateです。

ダブルバッファーのどのコピーが使用されているかを追跡するためにstate_index、更新スレッドと描画スレッドの両方で使用可能な変数を格納します。

更新スレッドはまず、独自のデータ(必要なデータ構造)を使用してオブジェクトのすべてのプロパティを計算します。そして、そのコピーcurrent_state[state_index]previous_state[state_index]、描画のための新たなデータの関連、およびコピーpositionおよびrotationcurrent_state[state_index]。それからstate_index = 1 - state_index、ダブルバッファの現在使用されているコピーを反転します。

上記のパラグラフのすべてはロックを解除して行わなければなりません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上記の機能がわからない場合は、ウィキペディアの主題に関する記事「線形補間」を参照してください。ただし、lerpingが何であるかわからない場合は、おそらく、補間された描画を使用した分離された更新/描画を実装する準備ができていません。


1
+1は、同じ方向を/回転と他のすべての状態のために行われなければならないその経時変化、すなわち粒子系などで材料アニメーションのような
はMaik Semder

1
良い点、マイク、私は位置を例として使用しました。外挿を使用する場合は、外挿したいプロパティの「速度」(つまり、そのプロパティの経時変化率)を保存する必要があります。最後に、外挿が内挿よりも優れている状況を本当に考えることはできません。質問者の質問がそれを要求したので、私はそれを含めました。補間を使用します。補間では、あなたが言ったように、補間するプロパティの現在および以前の更新結果を保存する必要があります。
-Olhovsky

これは問題の修正と、補間と外挿の違いです。それは答えではありません。

1
私の例では、状態の位置と回転を保存しました。状態に速度(または速度)を保存することもできます。次に、まったく同じ方法で速度を切り替えます(Lerp(previous_speed, current_speed, elapsed/update_tick_length))。これは、州に保存する任意の番号で実行できます。レルピング係数を指定すると、Lerpingは2つの値の間の値を提供します。
-Olhovsky

1
角運動の補間には、lerpではなくslerpを使用することをお勧めします。最も簡単なのは、両方の状態のクォータニオンを保存し、それらの間を強打することです。それ以外の場合は、角速度と角加速度に同じ規則が適用されます。骨格アニメーションのテストケースはありますか?
マイクセンダー

-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に経過したミリ秒数を掛けます。これは、ms(ミリ秒)の精度を提供するタイマーが必要であることを意味しますが、ほとんどのタイマーはこれを提供します。このようなことをするだけです:

var beginTime = getTimeInMillisecond()

...後で...

var time = getTimeInMillisecond()

varlapseTime = time-beginTime

beginTime =時間

...次に、このlapseedTimeを使用して、すべての距離を計算します。


1
彼には可変の更新レートはありません。彼の更新レートは固定されています。正直なところ、私はあなたがここで何をしようとしているのか本当に分かりません:/
Olhovsky

1
??? -1。それが全体のポイントです。更新レートは保証されていますが、レンダリングレートは可変であり、and音のない滑らかなものにしたいのです。
AttackingHobo

可変更新レートは、ネットワークゲーム、競合ゲーム、リプレイシステム、またはゲームプレイが決定論的であることに依存するその他のものではうまく機能しません。
AttackingHobo

1
修正された更新により、疑似摩擦の統合も容易になります。たとえば、各フレームに速度を0.9倍したい場合、高速または低速のフレームがある場合にどの程度乗算するかをどのように把握しますか?修正された更新が非常に好ましい場合があります-事実上すべての物理シミュレーションでは、修正された更新レートが使用されます。
オルホフスキー

2
可変フレームレートを使用し、多数のオブジェクトが互いに跳ね返る複雑な初期状態を設定した場合、まったく同じようにシミュレートされるという保証はありません。実際、ほとんどの場合、シミュレーションは毎回わずかに異なり、開始時にわずかな違いがあり、各シミュレーション実行間で短時間で完全に異なる状態になります。
AttackingHobo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.