Robust atan (y, x) auf GLSL zum Konvertieren der XY-Koordinate in den Winkel

8

In GLSL (speziell 3.00, die ich verwende) gibt es zwei Versionen von atan() : atan(y_over_x) kann nur Winkel zwischen -PI / 2, PI / 2 zurückgeben, während atan(y/x) alle 4 Quadranten berücksichtigen kann, also deckt der Winkelbereich alles von -PI, PI ab, ähnlich wie atan2() in C ++.

Ich möchte das zweite atan verwenden, um XY-Koordinaten in Winkel zu konvertieren. Allerdings ist atan() in GLSL, außerdem nicht in der Lage, wenn x = 0 zu handhaben, nicht sehr stabil. Insbesondere, wenn x nahe Null ist, kann die Division überlaufen, was zu einem entgegengesetzten resultierenden Winkel führt (Sie erhalten etwas in der Nähe von -PI / 2, wo Sie ungefähr PI / 2 erhalten).

Was ist eine gute, einfache Implementierung, die wir auf GLSL atan(y,x) aufbauen können, um sie robuster zu machen?

    
HuaTham 27.09.2014, 01:27
quelle

4 Antworten

6

Ich werde meine eigene Frage beantworten, um mein Wissen zu teilen. Wir bemerken zuerst, dass die Instabilität auftritt, wenn x nahe Null ist. Allerdings können wir das auch als abs(x) << abs(y) übersetzen. Zuerst teilen wir die Ebene (vorausgesetzt, wir befinden uns auf einem Einheitskreis) in zwei Regionen: eine mit |x| <= |y| und eine weitere mit |x| > |y| , wie unten gezeigt:

Wir wissen, dass atan(x,y) im grünen Bereich viel stabiler ist - wenn x nahe bei Null ist, haben wir einfach etwas in der Nähe von atan (0.0), das numerisch sehr stabil ist, während das übliche atan(y,x) stabiler ist in der orangen Region. Sie können sich auch davon überzeugen, dass diese Beziehung:

%Vor%

gilt für alle Nicht-Ursprung (x, y), wo es undefiniert ist, und wir sprechen über atan(y,x) , die Winkelwert im gesamten Bereich von -PI, PI, nicht atan(y_over_x) zurückgeben kann liefert nur Winkel zwischen -PI / 2, PI / 2. Daher ist unsere robuste atan2() -Routine für GLSL ziemlich einfach:

%Vor%

Als eine Randnotiz ist die Identität für die mathematische Funktion atan(x) tatsächlich:

%Vor%

was wahr ist, weil sein Bereich (-PI / 2, PI / 2) ist.

    
HuaTham 27.09.2014, 01:27
quelle
5

Abhängig von Ihrer Zielplattform könnte dies ein gelöstes Problem sein. Die OpenGL-Spezifikation für atan (y, x) gibt an, dass es in allen Quadranten funktionieren soll, wobei das Verhalten nur dann undefiniert bleibt, wenn x und y beide 0 sind.

Man würde also erwarten, eine anständige Implementierung in allen Achsen stabil zu halten, da dies der ganze Zweck hinter dem atan ist (oder atan2 ). stark>).

Der Fragesteller / die Fragestellerin ist insofern korrekt, als einige Implementierungen Abkürzungen verwenden. Die akzeptierte Lösung nimmt jedoch an, dass eine fehlerhafte Implementierung immer instabil ist, wenn x nahe Null ist: Auf einer Hardware (z. B. meinem Galaxy S4) ist der Wert stabil, wenn x nahe ist Null, aber instabil, wenn y nahe Null ist .

Um die Implementierung von atan(y,x) in Ihrem GLSL-Renderer zu testen, finden Sie hier ein WebGL-Testmuster. Folgen Sie dem unten stehenden Link, und solange Ihre OpenGL-Implementierung in Ordnung ist, sollten Sie Folgendes sehen:

Testmuster mit nativem atan(y,x) : Ссылка

Wenn alles in Ordnung ist, sollten Sie acht verschiedene Farben sehen (ignorieren Sie die Mitte).

Die verknüpften Demo-Beispiele atan(y,x) für mehrere Werte von x und y, einschließlich 0, sehr große und sehr kleine Werte. Die zentrale Box ist atan(0.,0.) - undefiniert mathematisch und Implementierungen variieren. Ich habe 0 (rot), PI / 2 (grün) und NaN (schwarz) auf der Hardware gesehen, die ich getestet habe.

Hier ist eine Testseite für die akzeptierte Lösung. Hinweis: Die WebGL-Version des Hosts fehlt mix(float,float,bool) . Daher habe ich eine Implementierung hinzugefügt, die der Spezifikation entspricht.

Testmuster mit atan2(y,x) von akzeptierter Antwort: Ссылка

    
Jonathan Lidbeck 10.07.2015 20:00
quelle
4

Ihre vorgeschlagene Lösung schlägt immer noch im Fall x=y=0 fehl. Hier geben beide Funktionen atan() NaN zurück.

Außerdem würde ich mich nicht auf Mix verlassen, um zwischen den beiden Fällen zu wechseln. Ich bin mir nicht sicher, wie dies implementiert / kompiliert wird, aber IEEE-Float-Regeln für x * NaN und x + NaN resultieren wiederum in NaN. Wenn Ihr Compiler also Mix / Interpolation verwendet, sollte das Ergebnis NaN für x=0 oder y=0 sein.

Hier ist ein weiterer Fix, der das Problem für mich gelöst hat:

%Vor%

Wenn x=0 , kann der Winkel ± π / 2 sein. Welche der beiden hängt nur von y ab. Wenn auch y=0 , kann der Winkel beliebig sein (Vektor hat die Länge 0). sign(y) gibt in diesem Fall 0 zurück, was einfach ok ist.

    
Johannes Jendersie 01.12.2014 12:51
quelle
3

Manchmal ist der beste Weg, um die Leistung eines Stücks Code zu verbessern, den Aufruf an erster Stelle zu vermeiden. Einer der Gründe, warum Sie beispielsweise den Winkel eines Vektors bestimmen möchten, besteht darin, dass Sie mit diesem Winkel eine Rotationsmatrix erstellen können, die Kombinationen aus Sinus und Kosinus des Winkels verwendet. Der Sinus und Kosinus eines Vektors (relativ zum Ursprung) sind jedoch bereits innerhalb des Vektors verborgen. Sie müssen lediglich eine normalisierte Version des Vektors erstellen, indem Sie jede Vektorkoordinate durch die Gesamtlänge des Vektors dividieren. Hier ist das zweidimensionale Beispiel, um den Sinus und Kosinus des Winkels des Vektors [x y] zu berechnen:

%Vor%

Sobald Sie die Sinus- und Kosinuswerte haben, können Sie nun eine Rotationsmatrix mit diesen Werten direkt befüllen, um beliebige Vektoren um den gleichen Winkel im oder gegen den Uhrzeigersinn zu drehen, oder Sie können eine zweite Rotationsmatrix verketten, um sie zu drehen Winkel ungleich Null. In diesem Fall können Sie sich die Rotationsmatrix als "Normalisierung" des Winkels auf Null für einen beliebigen Vektor vorstellen. Dieser Ansatz ist auch auf den dreidimensionalen (oder N-dimensionalen) Fall erweiterbar, obwohl Sie zum Beispiel drei Winkel und sechs sin / cos-Paare für die 3D-Rotation berechnen müssen (einen Winkel pro Ebene).

In Situationen, in denen Sie diesen Ansatz verwenden können, erhalten Sie einen großen Gewinn, indem Sie die Atan-Berechnung vollständig umgehen. Dies ist möglich, da Sie den Winkel nur aus den Sinus- und Kosinuswerten berechnen wollten. Indem Sie die Umwandlung in den Winkelraum und zurück überspringen, vermeiden Sie nicht nur, sich um die Division durch Null zu sorgen, sondern verbessern auch die Präzision für Winkel, die nahe den Polen liegen und ansonsten unter der Multiplikation / Division durch große Zahlen leiden würden. Ich habe diesen Ansatz erfolgreich in einem GLSL-Programm verwendet, das eine Szene auf Null Grad dreht, um eine Berechnung zu vereinfachen.

Es kann leicht sein, sich so schnell in ein Problem zu verstricken, dass Sie aus den Augen verlieren können, warum Sie diese Informationen von Anfang an benötigen. Nicht, dass das in jedem Fall funktioniert, aber manchmal hilft es, über den Tellerrand hinaus zu denken ...

    
Steve Phillips 13.10.2015 20:30
quelle