重力計算の最適化


23

互いに引き寄せられるサイズと速度がさまざまなオブジェクトがたくさんあります。更新するたびに、すべてのオブジェクトを調べて、他のすべてのオブジェクトの重力による力を加算する必要があります。それはあまりうまくスケールせず、ゲームで見つけた2つの大きなボトルネックの1つであり、パフォーマンスを改善するために何をすべきかわかりません。

パフォーマンスを改善できるはずだと感じています。常に、システム内のオブジェクトの99%がオブジェクトに与える影響はごくわずかです。もちろん、オブジェクトを質量でソートすることはできず、力は質量よりも距離によって大きく変化するため、上位10個の最大オブジェクトまたは何かのみを考慮することができます(方程式はに沿っていますforce = mass1 * mass2 / distance^2)。何かに影響を与えないかもしれない世界の反対側にある何百もの小さな岩の断片を無視して、最大のオブジェクト最も近いオブジェクトを考慮することは良い近似だと思いますが、どのオブジェクトが最も近い私はすべてのオブジェクトを反復処理する必要があり、それらの位置は絶えず変化しています、それで私が一度だけできるのではない。

現在、私はこのようなことをしています:

private void UpdateBodies(List<GravitatingObject> bodies, GameTime gameTime)
{
    for (int i = 0; i < bodies.Count; i++)
    {
        bodies[i].Update(i);
    }
}

//...

public virtual void Update(int systemIndex)
{
    for (int i = systemIndex + 1; i < system.MassiveBodies.Count; i++)
    {
        GravitatingObject body = system.MassiveBodies[i];

        Vector2 force = Gravity.ForceUnderGravity(body, this);
        ForceOfGravity += force;
        body.ForceOfGravity += -force;
    }

    Vector2 acceleration = Motion.Acceleration(ForceOfGravity, Mass);
    ForceOfGravity = Vector2.Zero;

    Velocity += Motion.Velocity(acceleration, elapsedTime);
    Position += Motion.Position(Velocity, elapsedTime);
}

(たとえば、衝突テストなど、多くのコードを削除したことに注意してください。衝突を検出するためにオブジェクトを2回繰り返し処理することはありません)。

そのため、リスト全体を常に繰り返すわけではありません-最初のオブジェクトに対してのみそれを行います。オブジェクトが別のオブジェクトに向かって感じる力を見つけるたびに、他のオブジェクトは同じ力を感じるので、両方を更新しますそれら-そして、その最初のオブジェクトは、更新の残りのために再び考慮される必要はありません。

Gravity.ForceUnderGravity(...)そしてMotion.Velocity(...)、などの機能がちょうどXNAのベクトル数学に建てられたのビットを使用しています。

2つのオブジェクトが衝突すると、質量のない破片が作成されます。それは別のリストに保持され、速度計算の一部として大規模なオブジェクトはデブリを反復しませんが、デブリの各断片は大規模な粒子を反復する必要があります。

これは、信じられないほどの限界まで拡大する必要はありません。世界は無制限ではなく、それを横切るオブジェクトを破壊する境界線を含んでいます-おそらく1000程度のオブジェクトを処理できるようにしたいのですが、現在、ゲームは200前後で窒息し始めています。

これをどのように改善できるかについての考えはありますか?ループの長さを数百から数個に削減するために使用できるヒューリスティックな方法はありますか?すべての更新よりも頻繁に実行できないコードがありますか?まともなサイズの世界を可能にするのに十分な速度になるまで、マルチスレッドするだけですか?速度計算をGPUにオフロードする必要がありますか?もしそうなら、私はそれをどのように設計しますか?GPUに静的な共有データを保持できますか?GPUでHLSL関数を作成し、それらを(XNAを使用して)任意に呼び出すことができますか、それとも描画プロセスの一部にする必要がありますか?


1
ご注意ください、「速度計算の一部として、大規模なオブジェクトはデブリを反復処理しませんが、デブリの各断片は大規模な粒子を反復処理する必要があります」と述べました。あなたはそれがより効率的であると仮定しているという印象を受けました。ただし、100個の破片オブジェクトをそれぞれ10回繰り返すことは、10個の大規模なオブジェクトを100回繰り返すことと同じです。おそらく、大規模なオブジェクトループ内の各残骸オブジェクトを反復処理することをお勧めします。
リチャードマースケル-ドラッカー

シミュレーションはどれほど正確ですか?お互いに向かって加速するものすべてが本当に必要ですか?そして、あなたは本当に真の重力計算を使用する必要がありますか?それとも、あなたが達成しようとしていることのためにそれらの慣習から離れることができますか?
カオス技術者

@Drackirあなたは正しいと思う。それらが分離される理由の一部は、質量のないオブジェクトでは数学が異なるためであり、一部は元々重力にまったく従わなかったためであり、それらを含まない方が効率的でした。それは痕跡です
カーソンマイヤーズ

@chaosTechnicianは非常に正確である必要はありません。実際、少数の最も支配的な力のみを考慮すれば、システムはより安定します。これは理想的です。しかし、私が困っている効率的な方法で、どの勢力が最も支配的であるかを見つけています。また、重力計算は既に近似されています。これG * m1 * m2 / r^2はGです。ここで、Gは動作を微調整するためのものです。(ユーザーがシステムを邪魔する可能性があるため、パスをたどることはできませんが)
カーソンマイヤーズ

質量のない破片が質量のある粒子を繰り返し処理する必要があるのはなぜですか?衝突?
サムホセバー

回答:


26

これはグリッドの仕事のように聞こえます。ゲーム空間をグリッドに分割し、グリッドセルごとに、現在その中にあるオブジェクトのリストを保持します。オブジェクトがセルの境界を越えて移動する場合、そのオブジェクトが含まれるリストを更新します。オブジェクトを更新し、相互作用する他のオブジェクトを検索する場合、現在のグリッドセルといくつかの隣接セルのみを確認できます。最高のパフォーマンスを得るためにグリッドのサイズを微調整できます(グリッドセルを更新するコスト-グリッドセルが小さすぎる場合は高い-グリッドセルが大きすぎる場合は検索を行うコスト)大)。

もちろん、これにより、2つのグリッドセルよりも離れたオブジェクトはまったく相互作用しなくなります。これは、質量の大きな蓄積(大きなオブジェクト、または多数の小さなオブジェクトのクラスター)が必要になるためです。 、あなたが述べたように、より大きな影響領域を持っています。

できることの1つは、各グリッドセル内の総質量を追跡し、セル全体を1つのオブジェクトとして処理して、遠く離れた相互作用を実現することです。つまり、オブジェクトにかかる力を計算するとき、いくつかの近くのグリッドセル内のオブジェクトのオブジェクト間の直接の加速度を計算し、さらに遠くにある各グリッドセルのセル間加速度を追加します(または多分、無視できない量の質量が含まれているものだけです)。セル間の加速度とは、2つのセルの総質量とそれらの中心間の距離を使用して計算されたベクトルを意味します。これにより、グリッドセル内のすべてのオブジェクトの合計重力の合理的な近似が得られますが、はるかに安価です。

ゲームの世界が非常に大きい場合、クアッドツリー(2D)やオクツリー(3D)などの階層グリッドを使用して、同様の原則を適用することもできます。長距離の相互作用は、階層の上位レベルに対応します。


1
グリッドアイデアの場合は+1。グリッドの重心も追跡することをお勧めします(必要に応じて)計算をもう少し純粋に保ちます。
カオス技術者

私はこのアイデアがとても好きです。オブジェクトをセルに含めることを検討していましたが、技術的に異なるセルにある2つの近くのオブジェクトを検討する際に放棄しましたが、いくつかの隣接するセルを検討するだけでなく、他のセルセル。私が正しくやれば、これは本当にうまくいくと思う。
カーソンマイヤーズ

8
これは基本的に、Barnes-Hutアルゴリズムです:en.wikipedia.org/wiki/Barnes –Hut_simulation
ラッセル・

物理学で重力がどのように機能するのか、つまり時空の曲がりに似ているようにさえ聞こえます。
ザンリンクス

私はこのアイデアが好きです-しかし、正直に言って、もう少し洗練が必要だと思います-2つのオブジェクトが互いに非常に近いが別々のセルにある場合はどうなりますか?3番目の段落がどのように役立つかはわかりましたが、相互作用するオブジェクトに円形のカリングを行うだけではどうですか?
ジョナサンディキンソン

16

Barnes-Hutアルゴリズムは、これを使用する方法です。あなたの正確な問題を解決するために、スーパーコンピューターのシミュレーションで使用されています。コーディングはそれほど難しくなく、非常に効率的です。この問題を解決するために、少し前に実際にJavaアプレットを作成しました。

http://mathandcode.com/programs/javagrav/にアクセスし、「start」と「show quadtree」を押します。

[オプション]タブでは、パーティクル数が最大200,000まで進むことがわかります。私のコンピューターでは、計算は約2秒で終了します(200,000ドットの描画には約1秒かかりますが、計算は別のスレッドで実行されます)。

アプレットの仕組みは次のとおりです。

  1. ランダムな粒子、ランダムな質量、位置、開始速度のリストを作成します。
  2. これらのパーティクルから四分木を構築します。各四分木ノードには、各サブノードの重心が含まれます。基本的に、各ノードには、massx、massy、massの3つの値があります。特定のノードにパーティクルを追加するたびに、massxおよびmassyをそれぞれparticle.x * particle.massおよびparticle.y * particle.massずつ増加させます。位置(massx / mass、massy / mass)は、ノードの重心になります。
  3. 各パーティクルについて、力を計算します(ここで詳しく説明します)。これは、最上位ノードから開始し、指定されたサブノードが十分に小さくなるまで、クアッドツリーの各サブノードを再帰処理することによって行われます。再帰を停止すると、粒子からノードの重心までの距離を計算でき、ノードの質量と粒子の質量を使用して力を計算できます。

あなたのゲームは、相互に引き付け合う何千ものオブジェクトを簡単に処理できるはずです。各オブジェクトが(私のアプレットのベアボーンパーティクルのように)「ダム」である場合、8000〜9000のパーティクルを取得できるはずです。そして、これはシングルスレッドを想定しています。マルチスレッドまたは並列コンピューティングアプリケーションを使用すると、リアルタイムで更新するよりも多くのパーティクルを取得できます。

こちらもご覧くださいhttp : //www.youtube.com/watch?v= XAlzniN6L94この大規模なレンダリング


最初のリンクは無効です。アプレットは他の場所でホストされていますか?
アンコ

1
一定!申し訳ありませんが、そのドメインで家賃を支払うのを忘れて、誰かがそれを自動で購入しました:\また、1.3年前の投稿8D

追加する必要があります。ソースコードがありません。いくつかのソースコードを探している場合は、part-nd(cで記述)を確認してください。他にもきっとあるはずです。

3

Nathan Reedが優れた答えを持っています。ショートバージョンは、シミュレーションのトポロジに適合するブロードフェーズテクニックを使用し、相互に顕著な影響を与えるオブジェクトのペアに対してのみ重力計算を実行することです。定期的な衝突検出のブロードフェーズで行うことと実際には違いはありません。

ただし、それを続けると、オブジェクトを断続的に更新するだけの可能性があります。基本的に、各タイムステップ(フレーム)はすべてのオブジェクトの一部のみを更新し、他のオブジェクトの速度(または好みに応じて加速度)は同じままにします。ユーザーは、間隔が長すぎない限り、これによる更新の遅延に気付くことはほとんどありません。これにより、アルゴリズムが直線的に高速化されるため、Nathanが提案したような幅広いフェーズの手法を必ず検討してください。大量のオブジェクトがある場合は、はるかに高速化できます。まったく同じようにモデリングされているわけではありませんが、これは一種の「重力波」のようなものです。:)

また、1回のパスで重力場を生成し、2回目のパスでオブジェクトを更新することもできます。最初のパスでは、基本的に各オブジェクトの重力の影響でグリッド(またはより複雑な空間データ構造)を埋めています。結果は重力場になり、任意の場所でオブジェクトに適用される加速度を確認するためにレンダリングすることもできます(かなりクールに見えます)。次に、オブジェクトを反復処理し、重力場の効果をそのオブジェクトに適用します。さらにクールなのは、オブジェクトを円/球としてテクスチャにレンダリングし、テクスチャを読み取って(またはGPUで別の変換フィードバックパスを使用して)オブジェクトの速度を変更することにより、GPUでこれを行うことができることです。


プロセスをパスに分割することは、優れたアイデアです。なぜなら、更新間隔は(私が知る限り)1秒の非常に短い時間だからです。重力場のテクスチャは素晴らしいですが、おそらく今のところ私の手の届かないところです。
カーソンマイヤーズ

1
最後の更新以降にスキップされたタイムスライスの数で、適用された力を乗算することを忘れないでください。
ザンリンクス

@seanmiddlemitch:重力場のテクスチャについてもう少し詳しく教えてください。申し訳ありませんが、私はグラフィックプログラマーではありませんが、これは本当に面白いですね。私はそれがどのように機能するのか理解していない。そして/あるいは、あなたはテクニックの説明へのリンクを持っていますか?
フェリックスドンベック

@FelixDombek:影響範囲を表す円としてオブジェクトをレンダリングします。フラグメントシェーダーは、オブジェクトの中心を指し、適切な大きさ(オブジェクトの中心と質量に基づいて)を指すベクトルを書き込みます。ハードウェアブレンドは、これらのベクトルの加算モードでの加算を処理できます。結果は正確な重力場ではありませんが、ほぼ確実にゲームのニーズには十分です。:GPUを使用してさらに別の方法として、このCUDAベースのN体重力シミュレーション技術参照http.developer.nvidia.com/GPUGems3/gpugems3_ch31.html
ショーンMiddleditch

3

クワッドツリーの使用をお勧めします。これらを使用すると、任意の長方形領域内のすべてのオブジェクトをすばやく効率的に検索できます。Wikiの記事は次のとおりです。http//en.wikipedia.org/wiki/Quadtree

そして、SourceForgeの私自身のXNA Quad Treeプロジェクトへの恥知らずなリンク:http : //sourceforge.net/projects/quadtree/

また、すべての大きなオブジェクトのリストを保持して、距離に関係なくすべてのオブジェクトとやり取りできるようにします。


0

ほんの少しの(おそらく素朴な)入力。私はゲームのプログラミングはしていませんが、私が感じていることは、あなたの根本的なボトルネックは重力による重力計算です。各オブジェクトXを反復処理してから各オブジェクトYから重力効果を見つけて追加する代わりに、各ペアX、Yを取得してそれらの間の力を見つけることができます。これにより、O(n ^ 2)から重力計算の数が削減されます。その後、多くの追加(O(n ^ 2))を行うことになりますが、これは通常は安価です。

また、この時点で、「これらの物体が小さすぎるために重力が\ epsilon未満になる場合、力をゼロに設定する」などのルールを実装できます。この構造を他の目的(衝突検出を含む)にも使用すると有利な場合があります。


これは基本的に私がやっていることです。Xを含むすべてのペアを取得した後、Xを繰り返し処理することはありません。XとY、XとZなどの間の力を見つけ、その力をペアの両方のオブジェクトに適用します。ループが完了すると、ForceOfGravityベクトルはすべての力の合計になり、速度と新しい位置に変換されます。私は確かに重力計算は特に高価であることないんだけど、それが初めての目立つ量保存しないはずのしきい値を超えた場合、私は考えていないのチェック
カーソンマイヤーズ

0

seanmiddleditchの答えを拡張する際に、重力場のアイデアにいくらかの光(皮肉なのか?)を当てるかもしれないと思った。

まず、テクスチャーではなく、変更可能な値の離散フィールド(2次元配列)であると考えてはなりません。シミュレーションのその後の精度は、そのフィールドの解像度になる可能性があります。

オブジェクトをフィールドに導入すると、周囲のすべての値についてその重力ポテンシャルを計算できます。それにより、フィールドに重力シンクを作成します。

しかし、これらのポイントのうち、以前ほど多くまたは無効になる前に、いくつ計算する必要がありますか?おそらく多くはありません。32x32でさえ、各オブジェクトを反復処理するための重要なフィールドです。したがって、プロセス全体を複数のパスに分割します。それぞれ解像度(または精度)が異なります。

つまり、最初のパスでは、4x4グリッドで表されるオブジェクトの重力を計算できます。各セルの値は、空間の2D座標を表します。O(n * 4 * 4)小計の複雑さを与える。

2番目のパスは、64x64解像度の重力場でより正確になる場合があり、各セル値は空間の2D座標を表します。ただし、複雑さが非常に高いため、影響を受ける周囲のセルの半径を制限できます(おそらく、周囲の5x5セルのみが更新されます)。

追加の3番目のパスは、おそらく1024x1024の解像度で、高精度の計算に使用できます。1024x1024の個別の計算を実際に実行しているが、このフィールドの一部(6x6のサブセクションなど)のみを操作していることを思い出してください。

このように、更新の全体的な複雑さはO(n *(4 * 4 + 5 * 5 + 6 * 6))です。

次に、各オブジェクトの速度変化を計算するには、各重力フィールド(4x4、64x64、1024x1024)に対して、ポイントマスの位置をグリッドセルにマップし、そのグリッドセル全体の重力ポテンシャルベクトルを新しいベクトルに適用します。「レイヤー」または「パス」ごとに繰り返します。それらを一緒に追加します。これにより、良好な合成重力ベクトルが得られます。

したがって、全体の複雑さはO(n *(4 * 4 + 5 * 5 + 6 * 6)+ n)です。(複雑さのために)実際に重要なのは、パスの重力ポテンシャルを計算するときに更新する周囲のセルの数であり、重力場の全体的な解像度ではありません。

低解像度のフィールド(最初のパス)の理由は、明らかに宇宙全体を包含し、距離に関係なく周辺の質量がより高密度の領域に引き付けられるようにするためです。次に、より高い解像度のフィールドを個別のレイヤーとして使用して、隣接する惑星の精度を高めます。

これが理にかなったことを願っています。


0

別のアプローチはどうですか:

質量に基づいて影響範囲をオブジェクトに割り当てます。オブジェクトは、その範囲を超えて測定可能な効果を得るには小さすぎます。

ワールドをグリッドに分割し、各オブジェクトを、影響を受けるすべてのセルのリストに配置します。

重力計算は、オブジェクトが含まれるセルに添付されたリスト内のオブジェクトに対してのみ実行してください。

オブジェクトが新しいグリッドセルに移動したときにのみリストを更新する必要があります。

グリッドセルが小さいほど、更新ごとに行う計算は少なくなりますが、リストの更新により多くの作業を行います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.