14

GLSL (具体的には私が使用している 3.00) には 2 つのバージョンがあります atan(): atan(y_over_x)-PI/2、PI/2 の間の角度のみを返すことatan(y/x)ができますが、4 つの象限すべてを考慮に入れることができるため、角度範囲は -PI からすべてをカバーします。 PI、 atan2()C++とよく似ています。

atan2番目を使用してXY座標を角度に変換したいと思います。ただし、atan()GLSL では、 の場合に処理できないほか、x = 0あまり安定していません。特に がxゼロに近い場合、除算がオーバーフローして結果の角度が反対になる可能性があります (約 PI/2 になるはずの場所で -PI/2 に近い値が得られます)。

atan(y,x)より堅牢にするために GLSL の上に構築できる優れた単純な実装は何ですか?

4

5 に答える 5

13

自分の知識を共有するために、自分の質問に答えます。xがゼロに近いときに不安定性が発生することに最初に気付きます。ただし、それを と翻訳することもできabs(x) << abs(y)ます。そのため、最初に平面を (単位円上にあると仮定して) 2 つの領域に分割します。1 つは where |x| <= |y|、もう1 つは where|x| > |y|です。

2つの地域

atan(x,y)緑色の領域でははるかに安定していることがわかります。x がゼロに近い場合、数値的に非常に安定している atan(0.0) に近いものがあるだけですが、通常atan(y,x)はオレンジ色の領域でより安定しています。この関係を次のように納得させることもできます。

atan(x,y) = PI/2 - atan(y,x)

未定義のすべての非原点 (x,y) に適用されます。これは、-PI/2 の間の角度のみを返すのではatan(y,x)なく、-PI,PI の範囲全体で角度値を返すことができるということです。 atan(y_over_x)PI/2。したがって、atan2()GLSL の堅牢なルーチンは非常に単純です。

float atan2(in float y, in float x)
{
    bool s = (abs(x) > abs(y));
    return mix(PI/2.0 - atan(x,y), atan(y,x), s);
}

補足として、数学関数のアイデンティティatan(x)は実際には次のとおりです。

atan(x) + atan(1/x) = sgn(x) * PI/2

その範囲が (-PI/2, PI/2) であるため、これは真です。

グラフ

于 2014-09-27T01:27:09.607 に答える
12

対象のプラットフォームによっては、これで問題が解決する場合があります。atan(y, x)の OpenGL 仕様では、x と y が両方とも 0 の場合にのみ動作を未定義のままにして、すべての象限で機能する必要があると指定しています。

したがって、これが 2 引数atan (またはatan2 )の背後にある全体的な目的であるため、まともな実装はすべての軸の近くで安定していると期待できます。

一部の実装ではショートカットを使用するという点で、質問者/回答者は正しいです。ただし、受け入れられた解決策では、 x がゼロに近い場合、不適切な実装は常に不安定になるという前提があります。一部のハードウェア (たとえば、私の Galaxy S4) では、xがゼロに近い場合は値が安定しますが、 yがゼロに近い場合は不安定になります。

GLSL レンダラーの の実装をテストするためにatan(y,x)、WebGL テスト パターンを次に示します。以下のリンクに従ってください。OpenGL の実装が適切である限り、次のように表示されます。

GLSL atan(y,x) テスト パターン

ネイティブを使用したテスト パターンatan(y,x): http://glslsandbox.com/e#26563.2

すべてが順調であれば、8 つの異なる色が表示されるはずです (中心を無視)。

atan(y,x)0、非常に大きな値、非常に小さな値など、x と y のいくつかの値のリンクされたデモ サンプル。中央のボックスはatan(0.,0.)数学的に --undefined であり、実装はさまざまです。私がテストしたハードウェアでは、0 (赤)、PI/2 (緑)、および NaN (黒) が見られました。

これは、承認されたソリューションのテスト ページです。注:ホストの WebGL バージョンには が欠けているmix(float,float,bool)ため、仕様に一致する実装を追加しました。

atan2(y,x)受け入れられた回答を使用したテスト パターン: http://glslsandbox.com/e#26666.0

于 2015-07-10T20:00:02.643 に答える
8

提案された解決策は、ケースではまだ失敗しますx=y=0。ここでは、両方のatan()関数が NaN を返します。

さらに、2 つのケースを切り替えるためにミックスに依存することはありません。これがどのように実装/コンパイルされているかはわかりませんが、x*NaN と x+NaN の IEEE float ルールは再び NaN になります。したがって、コンパイラが実際に mix/interpolation を使用した場合、結果はx=0orに対して NaN になるはずy=0です。

これが私の問題を解決した別の修正です:

float atan2(in float y, in float x)
{
    return x == 0.0 ? sign(y)*PI/2 : atan(y, x);
}

x=0角度が ±π/2 の場合。2 つのうちどちらがのみに依存しyます。その場合y=0、角度は任意にすることができます (ベクトルの長さは 0 です)。その場合sign(y)は戻りますが、それで問題ありません。0

于 2014-12-01T12:51:47.340 に答える
5

コードの一部のパフォーマンスを改善する最善の方法は、最初からコードを呼び出さないようにすることです。たとえば、ベクトルの角度を決定する理由の 1 つは、この角度を使用して、角度のサインとコサインの組み合わせを使用して回転行列を作成できるようにするためです。ただし、ベクトルのサインとコサイン (原点に対する) は、ベクトル自体の内部にすでに隠されています。必要なことは、各ベクトル座標をベクトルの全長で割って、正規化されたバージョンのベクトルを作成することだけです。ベクトル [ xy ] の角度の正弦と余弦を計算する 2 次元の例を次に示します。

double length = sqrt(x*x + y*y);
double cos = x / length;
double sin = y / length;

正弦値と余弦値を取得したら、回転行列にこれらの値を直接入力して、同じ角度で任意のベクトルの時計回りまたは反時計回りの回転を実行したり、2 番目の回転行列を連結して、それ以外の角度に回転したりできます。ゼロ。この場合、回転行列は、任意のベクトルの角度をゼロに「正規化」するものと考えることができます。このアプローチは、3 次元 (または N 次元) の場合にも拡張可能ですが、たとえば、3D 回転の計算には 3 つの角度と 6 つの sin/cos ペア (平面ごとに 1 つの角度) が必要です。

このアプローチを使用できる状況では、atan 計算を完全にバイパスすることで大きな勝利を収めることができます。これは、角度を決定したかった唯一の理由がサイン値とコサイン値を計算することだったからです。角度空間への変換とその逆への変換をスキップすることで、ゼロ除算を心配する必要がなくなるだけでなく、極に近い角度の精度も向上します。シーンをゼロ度に回転させて計算を簡素化するGLSLプログラムでこのアプローチをうまく使用しました。

差し迫った問題に巻き込まれ、そもそもなぜこの情報が必要なのかを見失う可能性があります。これがすべての場合に機能するわけではありませんが、箱から出して考えると役立つ場合があります...

于 2015-10-13T20:30:30.300 に答える