距離関数を最適化する方法は?


23

かなり単純なRTSのようなゲームを開発しているときに、距離の計算がパフォーマンスに影響を与えていることに気付きました。

常に、ユニットがターゲットの範囲内にあるかどうか、発射体がターゲットに到達したかどうか、プレイヤーがピックアップを乗り越えたか、一般的な衝突などがあるかを知るための距離チェックがあります。 2点間の距離が多く使用されます。

私の質問はまさにそれについてです。フレームごとに何千回も実行している場合、かなり時間がかかる通常のsqrt(x * x + y * y)アプローチ以外に、ゲーム開発者が距離をチェックするための代替手段を知りたいです。

マンハッタン距離と二乗距離の比較に気づいていることを指摘したい(sqrtボトルネックをスキップすることによって)。他に何か?



たとえば、建物のように移動することを期待していないオブジェクトがある場合、距離関数の2Dテイラーシリーズを取得し、それを二乗項で切り捨てて、結果の関数をその特定の建物からの距離関数。これにより、面倒な作業の一部が初期化に移され、処理が少し速くなる可能性があります。
アレクサンダーグルーバー14

回答:


26

TL; DR; 問題は距離機能の実行にありません。問題は、距離関数を何度も実行することです。つまり、数学的な最適化ではなく、アルゴリズムによる最適化が必要です。

[編集]人々がそれを嫌っているので、私は答えの最初のセクションを削除しています。質問のタイトルは、編集前に代替距離関数を求めていました。

平方根を毎回計算する距離関数を使用しています。それでも、平方根をまったく使用せずに単純に置き換えて、代わりに平方距離を計算できます。これにより、貴重なサイクルを大幅に節約できます。

距離^ 2 = x * x + y * y;

これは実際には一般的なトリックです。ただし、それに応じて計算を調整する必要があります。実際の距離を計算する前の初期チェックとしても使用できます。 そのため、たとえば、交差テストの2点/球間の実際の距離を計算する代わりに、代わりに距離の2乗を計算し、半径ではなく半径の2乗と比較できます。

@ Byte56が質問を読んでおらず、距離の二乗最適化に気付いていることを指摘した後、編集してください。

あなたの場合、残念ながら、私たちはほとんど排他的にユークリッド空間を扱っているコンピューターグラフィックスであり、距離はSqrt of Vector dot itselfユークリッド空間のように正確に定義されています。

距離の2乗は(パフォーマンスの観点から)取得する最良の近似であり、2回の乗算、1回の加算、および割り当てを打つものは見当たりません。

だから、距離関数を最適化できないと言ったらどうすればいいですか?

問題は距離機能の実行にありません。問題は、距離関数を何度も実行することです。つまり、数学的な最適化ではなく、アルゴリズムによる最適化が必要です。

ポイントは、プレイヤーがシーン内の各オブジェクトと交差するのではなく、各フレームをチェックすることです。空間的一貫性を簡単に活用して、プレーヤーの近くにあるオブジェクト(ヒット/交差する可能性が最も高いオブジェクト)のみをチェックできます。

これは、これらの空間情報を空間分割データ構造に実際に格納することで簡単に実行できます。単純なゲームの場合、基本的に実装が簡単で、動的なシーンにうまくフィットするため、グリッドをお勧めします。

すべてのセル/ボックスには、グリッドの境界ボックスが囲むオブジェクトのリストが含まれます。そして、それらのセル内のプレーヤーの位置を追跡するのは簡単です。また、距離の計算では、シーン内のすべてではなく、同じセルまたは隣接セル内のオブジェクトとのプレーヤー距離のみをチェックします。

より複雑なアプローチは、BSPまたはOctreesを使用することです。


2
質問の最後の文は、OPが他の代替手段を探していると言っていると思います(二乗距離の使用について知っています)。
MichaelHouse

@ Byte56はい、あなたは正しいです、私はそれを読みませんでした。
concept3d 14年

とにかく答えてくれてありがとう。その方法ではユークリッド距離は得られませんが、比較では非常に正確であることを確認する文を追加しますか?これは、検索エンジンからここに来る人に何かを追加すると思います。
グリムショー14年

@Grimshaw私は元の問題に取り組むために答えを編集しました。
concept3d 14年

@ Byte56、指摘してくれてありがとう。答えを編集しました。
concept3d 14年

29

任意の距離にわたって線形のままでありdistance^2、(正方形のチェビシェフやダイヤモンドのようなマンハッタンの距離とは異なり)漠然と円形に見えるものが必要な場合は、後者の2つの手法を平均して八角形の距離の近似値を取得できます:

dx = abs(x1 - x0)
dy = abs(y1 - y0)

dist = 0.5 * (dx + dy + max(dx, dy))

Wolfram Alphaのおかげで、関数の視覚化(等高線図)ができました。

等高線図

そして、ユークリッド距離(ラジアン、第1象限のみ)と比較した場合の誤差関数のプロットは次のとおりです。

エラープロット

ご覧のとおり、エラーの範囲は軸上の0%からローブの約+ 12%までです。係数を少し変更することで、+ /-4%に下げることができます。

dist = 0.4 * (dx + dy) + 0.56 * max(dx, dy)

ここに画像の説明を入力してください

更新

上記の係数を使用すると、最大誤差は+/- 4%以内になりますが、平均誤差は+ 1.3%のままです。平均誤差がゼロになるように最適化されており、以下を使用できます。

dist = 0.394 * (dx + dy) + 0.554 * max(dx, dy)

-5%から+ 3%のエラーと+ 0.043%の平均エラーが発生します


このアルゴリズムの名前をWebで検索しているときに、次のような8角形近似が見つかりました。

dist = 1007/1024 * max(dx, dy) + 441/1024 * min(dx, dy)

これは本質的に同等であることに注意してください(指数は異なりますが、これらは-1.5%から7.5%の誤差を与えますが、+ /-4%にマッサージすることができます)max(dx, dy) + min(dx, dy) == dx + dy。このフォームを使用すると、minとのmax呼び出しを除外して次のことを行うことができます。

if (dy > dx)
    swap(dx, dy)

dist = 1007/1024 * dx + 441/1024 * dy

これは私のバージョンよりも速くなりますか?誰が知っている...コンパイラと、それがターゲットプラットフォーム用に最適化する方法に依存します。私の推測では、違いを見ることはかなり難しいでしょう。


3
興味深いことに、これを見たことがない!名前はありますか、それとも「チェビシェフとマンハッタンの平均」ですか?
コンガスボンガス14年

@congusbongus名前はおそらくありますが、何であるかはわかりません。ない場合は、おそらく1日には、(ほら...多分ない)クリスト距離と呼ばれる
bcrist

1
浮動小数点の乗算はあまり効率的ではないことに注意してください。これが、他の近似が1007/1024を使用する理由です(これは、整数の乗算とそれに続くビットシフトとして実装されます)。
MSalters

@MSaltersはい、浮動小数点演算は整数演算よりも遅いことがよくありますが、それは無関係です-0.4と0.56は整数演算を使用するように簡単に変換できます。さらに、現代のx86ハードウェア、ほとんどの浮動小数点演算に(以外FDIVFSQRTおよび他の超越関数)その整数バージョンと本質的に同じコスト:命令当たり1つの又は2サイクル。
bcrist 14年

1
これはAlpha max + Beta Minに非常に似ています:en.wikipedia.org/wiki/Alpha_max_plus_beta_min_algorithm
drake7707

21

この問題は、距離計算の実行コストではなく、計算が行われた回数が原因で発生する場合があります。

多くのアクターがいる大規模なゲームの世界では、あるアクターと他のすべてのアクター間の距離をチェックし続けることはスケーラブルではありません。より多くのプレイヤー、NPC、および発射体が世界に入ると、必要な比較の数はで二次的に増加します。O(N^2)

その成長を抑える1つの方法は、適切なデータ構造を使用して、不要なアクターを計算から迅速に破棄することです。

私たちは、効率的に反復すべての俳優への道を探している可能性がある一方で、範囲内とすることが過半数を除いている俳優の範囲外の間違いを

アクターがワールドスペース全体にかなり均等に分散している場合、バケットのグリッドが適切な構造である必要があります(受け入れられた答えが示唆するように)。アクターへの参照を粗いグリッドで保持することにより、範囲内にある可能性のあるすべてのアクターをカバーするために、近くのバケットのいくつかをチェックするだけで、残りは無視されます。アクターが移動するとき、古いバケツから新しいバケツに移動する必要がある場合があります。

ほぼ均等に分散している俳優たちのために四分木は、 2次元の世界のためのより良い行うことができ、または八分木は、 3次元の世界に適しているであろう。これらは、空のスペースの大きな領域と多くのアクターを含む小さな領域を効率的に分割できる、より汎用的な構造です。以下のために静的な役者がありバイナリ空間分割探索はなく、リアルタイムでの更新もはるかにコストがかかりすぎることは非常に高速です(BSP)、。BSPは、平面を使用して空間を分割し、繰り返し半分にカットし、任意の数の次元に適用できます。

もちろん、アクターをそのような構造に保つには、特にパーティション間を移動しているときにオーバーヘッドがあります。しかし、多くのアクターがいるが関心の範囲が狭い大規模な世界では、コストは、すべてのオブジェクトに対する単純な比較によって発生するコストよりもはるかに低くなければなりません。

スケーラブルなソフトウェア設計には、より多くのデータを受信するにつれてアルゴリズムの費用がどのように増大するかを考慮することが重要です。正しいデータ構造を選択するだけで十分な場合があります。通常、コストはBig O表記を使用して記述されます。

(これは質問に対する直接的な答えではないことは承知していますが、読者によっては役に立つかもしれません。時間を無駄にしてしまった場合はおologiesびします!)


7
これが最良の答えです。距離関数には最適化するものはありません。あまり頻繁に使用する必要はありません。
サムホセバー14年

3
受け入れられた回答は空間分割も対象とします。それ以外の場合、回答は本当に最適です。ありがとうございました。
グリムショー14年

私の時間はあなたの答えを読むのにとてもよく費やされました。ジョーイ、ありがとう。
パトリックM 14年

1
これが最良の答えであり、距離関数のパフォーマンスを赤くするのではなく、実際の問題に焦点を合わせている唯一の答えです。受け入れられた答えは空間分割もカバーしているかもしれませんが、余談です。距離の計算に焦点を当てています。ここで、距離の計算は主要な問題ではありません。距離計算の最適化は、スケーリングされない総当たりの非解決策です。
マキシマスミニマス14年

比較の数が指数関数的になる理由を説明してください。私はそれが二次的だろうが、各時間枠で各俳優を互いに比較します。
PetrPudlák14年

4

チェビシェフ距離はどうですか?ポイントp、qの場合、次のように定義されます。

距離

したがって、ポイント(2、4)および(8、5)の場合、チェビシェフ距離は6であり、| 2-8 |となります。> | 4-5 |。

さらに、Eをユークリッド距離、Cをチェビシェフ距離とします。次に:

距離2

平方根を計算する必要があるため、上限はおそらくあまり使用されませんが、下限は役立つ可能性があります-チェビシェフ距離が範囲外になるのに十分な大きさであれば、ユークリッド距離も必要です計算する必要がなくなります。

トレードオフは、もちろん、チェビシェフ距離が範囲内にある場合、とにかくユークリッド距離を計算する必要があり、時間を浪費することです。純利益になるかどうかを知る唯一の方法です!


1
マンハッタン距離を上限として使用することもできます。
コンガスボン14年

1
本当です。そこからは、bcristが示唆するように、「チェビシェフとマンハッタンの平均」へのホップ、スキップ、ジャンプだけだと思います。
テトリニティ14年

2

非常に単純なローカル最適化は、最初に単一のディメンションをチェックすることです。

あれは :

distance ( x1, y1 , x1, y2) > fabs (x2 - x1)

したがってfabs (x2 - x1)、最初のフィルターとしてチェックするだけで、かなりのゲインが得られる場合があります。どれくらいが世界のサイズ対関連する範囲に依存します。

さらに、これを空間分割データ構造の代替として使用できます。

すべての関連オブジェクトがx座標の順序でリストにソートされている場合、近くのオブジェクトはリストの近くになければなりません。オブジェクトの移動に伴ってリストが完全に維持されないためにリストの順序が狂った場合でも、既知の速度限界を考慮すると、リストのセクションを減らして近くのオブジェクトを検索できます。


2

過去に最適化の努力がなされましたsqrt。今日のマシンには適用されなくなりましたが、マジック番号 を使用するQuakeソースコードの例を次に示します0x5f3759df

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // what the hell?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration (optional)
  // ...
  return y;
}

詳細な説明ここで何が起こっているのは、Wikipediaで見つけることができます。

要するに、これはニュートン法(推定値を反復的に改善する数値アルゴリズム)数回の反復であり、妥当な初期推定値を提供するためにマジックナンバーが使用されます。

Travisが指摘しているように、この種の最適化は最新のアーキテクチャではもはや有用ではありません。たとえそうであったとしても、ボトルネックを一定の速度で高速化することしかできませんでしたが、アルゴリズムの再設計によってより良い結果が得られる可能性があります。


2
これはもはや価値のある最適化ではありません。最近購入できるほとんどすべての消費者グレードのPCアーキテクチャには、クロックサイクル以下で平方根を実行するハードウェア最適化sqrt命令があります。あなたが本当に可能な最速の平方根が必要な場合は、x86のを使用する浮動小数点SQRT命令SIMD:en.wikipedia.org/wiki/... GPUのシェーダのようなもののために、SQRTを呼び出すと、自動的にこのような命令になります。CPUでは、多くのコンパイラがSIMD sqrtを使用してsqrtを実装していると想定しています。
TravisG 14年

@TravisGはい、それは言及する価値があるので、答えを更新しました。この回答は、楽しさと歴史的な興味のためだけに提供されました!
joeytwiddle
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.