Ich drehe ein Objekt um zwei Achsen. Warum dreht es sich dann immer wieder um die dritte Achse?

38

Ich sehe, dass häufig Fragen auftauchen, die dieses zugrunde liegende Problem haben, aber sie sind alle in den Einzelheiten eines bestimmten Features oder Tools gefangen. Hier ist ein Versuch, eine kanonische Antwort zu erstellen, auf die wir Benutzer verweisen können, wenn dies eintritt - mit vielen animierten Beispielen! :)


Nehmen wir an, wir machen eine First-Person-Kamera. Die Grundidee ist, nach links und rechts zu schauen und nach oben und unten zu schauen. Also schreiben wir ein bisschen Code wie diesen (am Beispiel von Unity):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

oder vielleicht

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

Und es funktioniert meistens, aber mit der Zeit wird der Blick schief. Die Kamera scheint sich um ihre Rollachse (z) zu drehen, obwohl wir ihr nur befohlen haben, sich auf x und y zu drehen!

Animiertes Beispiel einer Kamera aus der ersten Person, die sich seitwärts neigt

Dies kann auch passieren, wenn wir versuchen, ein Objekt vor der Kamera zu manipulieren - sagen wir, es ist ein Globus, den wir drehen möchten, um uns umzusehen:

Animiertes Beispiel eines seitlich kippenden Globus

Das gleiche Problem - nach einer Weile beginnt der Nordpol nach links oder rechts zu wandern. Wir geben Input auf zwei Achsen, aber wir bekommen diese verwirrende Rotation auf einer dritten. Und es passiert, ob wir alle unsere Rotationen um die lokalen Achsen des Objekts oder die globalen Achsen der Welt anwenden.

In vielen Motoren sehen Sie dies auch im Inspektor - drehen Sie das Objekt in der Welt, und plötzlich ändern sich die Zahlen auf einer Achse, die wir nicht einmal berührt haben!

Animiertes Beispiel für die Manipulation eines Objekts neben einer Anzeige seiner Drehwinkel.  Der z-Winkel ändert sich, obwohl der Benutzer diese Achse nicht manipuliert hat.

Also, ist das ein Motorfehler? Wie können wir dem Programm mitteilen, dass es keine zusätzliche Rotation hinzufügen soll?

Hat es etwas mit Eulerwinkeln zu tun? Soll ich stattdessen Quaternionen oder Rotationsmatrizen oder Basisvektoren verwenden?

DMGregory
quelle
1
Ich fand die Antwort auf die folgende Frage auch sehr hilfreich. gamedev.stackexchange.com/questions/123535/…
Travis Pettry

Antworten:

51

Nein, dies ist kein Motorfehler oder ein Artefakt einer bestimmten Rotationsdarstellung (dies kann auch passieren, aber dieser Effekt gilt für jedes System, das Rotationen darstellt, einschließlich Quaternionen).

Sie haben eine reale Tatsache darüber entdeckt, wie Rotation im dreidimensionalen Raum funktioniert, und es weicht von unserer Intuition über andere Transformationen wie das Übersetzen ab:

Animiertes Beispiel, das zeigt, dass das Anwenden von Rotationen in einer anderen Reihenfolge unterschiedliche Ergebnisse liefert

Wenn wir Rotationen auf mehr als einer Achse erstellen, erhalten wir nicht nur den Gesamt- / Nettowert, den wir auf jede Achse angewendet haben (wie wir es für die Übersetzung erwarten würden). Die Reihenfolge, in der wir die Drehungen anwenden, ändert das Ergebnis, da jede Drehung die Achsen bewegt, auf die die nächsten Drehungen angewendet werden (wenn sie sich um die lokalen Achsen des Objekts drehen), oder die Beziehung zwischen dem Objekt und der Achse (wenn sie sich um die Welt drehen) Achsen).

Die Veränderung der Achsenbeziehungen im Laufe der Zeit kann unsere Intuition darüber verwirren, was jede Achse "tun" soll. Insbesondere bestimmte Kombinationen von Gier- und Nickrotationen führen zum gleichen Ergebnis wie eine Rollrotation!

Ein animiertes Beispiel, das eine Folge von lokalem Pitch-Yaw-Pitch zeigt, liefert dieselbe Ausgabe wie ein einzelnes lokales Würfeln

Sie können überprüfen, ob sich jeder Schritt korrekt um die von uns angeforderte Achse dreht. In unserer Notation gibt es keine Motorstörungen oder Artefakte, die unsere Eingabe beeinträchtigen oder nachträglich erraten herum "aufeinander. Sie mögen lokal orthogonal sein, für kleine Rotationen, aber wenn sie sich aufstapeln, stellen wir fest, dass sie nicht global orthogonal sind.

Dies ist am dramatischsten und klarsten für 90-Grad-Kurven wie oben, aber die wandernden Achsen kriechen auch über viele kleine Umdrehungen ein, wie in der Frage gezeigt.

Also, was machen wir dagegen?

Wenn Sie bereits über ein Pitch-Yaw-Rotationssystem verfügen, können Sie unerwünschte Rollbewegungen am schnellsten beseitigen, indem Sie eine der Rotationen so ändern, dass sie auf der globalen oder übergeordneten Transformationsachse und nicht auf der lokalen Achse des Objekts ausgeführt werden. Auf diese Weise können Sie keine Kreuzkontamination zwischen den beiden erhalten - eine Achse bleibt absolut kontrolliert.

Hier ist dieselbe Sequenz von Nick-Gier-Nick, die im obigen Beispiel zu einer Rolle wurde, aber jetzt wenden wir unser Gieren um die globale Y-Achse an, anstatt um die des Objekts

Animiertes Beispiel eines Bechers, der sich mit lokaler Neigung und globalem Gieren dreht, ohne RollproblemAnimiertes Beispiel einer First-Person-Kamera mit Global Yaw

So können wir die First-Person-Kamera mit dem Mantra "Pitch Local, Yaw Globally" reparieren:

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f, Space.Self);
}

Wenn Sie Ihre Rotationen mithilfe der Multiplikation kombinieren, müssen Sie die Links- / Rechtsreihenfolge einer der Multiplikationen umkehren, um den gleichen Effekt zu erzielen:

// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation =  yaw * transform.rotation; // yaw on the left.

// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.

(Die genaue Reihenfolge hängt von den Multiplikationskonventionen in Ihrer Umgebung ab, aber left = globaler / right = lokaler ist eine häufige Wahl.)

Dies ist gleichbedeutend mit dem Speichern der gewünschten Nettogier- und Gesamtsteigung als Float-Variablen und dem gleichzeitigen Anwenden des Nettoergebnisses, wobei nur aus diesen Winkeln eine einzelne neue Orientierungsquaternion oder -matrix erstellt wird (vorausgesetzt, Sie halten totalPitchsie fest):

// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;

oder äquivalent...

// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
                    Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
                    Mathf.sin(totalPitch),
                    Mathf.cos(totalPitch) * Mathf.cos(totalYaw));

// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;

Bei dieser globalen / lokalen Aufteilung haben die Rotationen keine Chance, sich zu verbinden und zu beeinflussen, da sie auf unabhängige Achsensätze angewendet werden.

Die gleiche Idee kann helfen, wenn es sich um ein Objekt in der Welt handelt, das wir drehen möchten. Bei einem Beispiel wie dem Globus möchten wir ihn oft umkehren und unser Gieren lokal anwenden (damit er sich immer um seine Pole dreht) und global neigen (damit er in Richtung / weg von unserer Sicht und nicht in Richtung / weg von Australien kippt , wohin es zeigt ...)

Animiertes Beispiel eines Globus mit besserer Rotation

Einschränkungen

Diese globale / lokale Hybridstrategie ist nicht immer die richtige Lösung. In einem Spiel mit 3D-Flug / Schwimmen möchten Sie möglicherweise direkt nach oben / unten zeigen und trotzdem die volle Kontrolle haben. Aber mit diesem Setup werden Sie auf Gimbal Lock klicken - Ihre Gierachse (global aufwärts) wird parallel zu Ihrer Rollachse (lokal vorwärts), und Sie haben keine Möglichkeit, nach links oder rechts zu schauen, ohne sich zu drehen.

In solchen Fällen können Sie stattdessen reine lokale Rotationen verwenden, wie wir sie in der obigen Frage beschrieben haben (damit sich Ihre Steuerelemente gleich anfühlen, egal wohin Sie schauen), wodurch sich zunächst ein wenig Roll einschleichen kann - aber dann wir korrigieren es.

Zum Beispiel können wir lokale Rotationen verwenden, um unseren "Vorwärts" -Vektor zu aktualisieren, und dann diesen Vorwärtsvektor zusammen mit einem Referenz- "Auf" -Vektor verwenden, um unsere endgültige Orientierung zu konstruieren. (Verwenden Sie zum Beispiel die Unity- Methode Quaternion.LookRotation oder konstruieren Sie manuell eine orthonormale Matrix aus diesen Vektoren.) Durch Steuern des Aufwärtsvektors steuern Sie die Roll- oder Verdrehung.

Für das Flug- / Schwimmbeispiel möchten Sie diese Korrekturen im Laufe der Zeit schrittweise anwenden. Wenn es zu abrupt ist, kann die Sicht störend schwanken. Stattdessen können Sie den aktuellen Aufwärtsvektor des Players verwenden und ihn Frame für Frame vertikal ausrichten, bis die Ansicht ausgeglichen ist. Das Anwenden dieser Option in einer Runde kann manchmal weniger Übelkeit verursachen als das Drehen der Kamera, während die Bedienelemente des Players inaktiv sind.

DMGregory
quelle
1
Darf ich fragen, wie du Gifs gemacht hast? Sie sehen gut aus.
Bálint
1
@ Bálint Ich benutze ein kostenloses Programm namens LICEcap , mit dem Sie einen Teil Ihres Bildschirms als GIF aufzeichnen können. Dann schneide ich die Animation zu / füge zusätzlichen Beschriftungstext hinzu / komprimiere das GIF mit Photoshop.
DMGregory
Ist diese Antwort nur für die Einheit? Gibt es eine allgemeinere Antwort im Web?
posfan12
2
@ posfan12 Der Beispielcode verwendet aus Gründen der Vollständigkeit die Unity-Syntax, aber die damit verbundenen Situationen und Berechnungen gelten auf der ganzen Linie gleichermaßen. Haben Sie Schwierigkeiten, die Beispiele auf Ihre Umgebung anzuwenden?
DMGregory
2
Die Mathematik ist bereits oben vorhanden. Nach deiner Bearbeitung zu urteilen, machst du nur die falschen Teile daraus. Anscheinend verwenden Sie den hier definierten Vektortyp . Dies beinhaltet ein Verfahren zum Konstruieren einer Rotationsmatrix aus einem Satz von Winkeln, wie oben beschrieben. Beginnen Sie mit einer Identitätsmatrix und rufen Sie auf TCVector::calcRotationMatrix(totalPitch, totalYaw, identity)- dies entspricht dem obigen Beispiel "Alles auf einmal Euler".
DMGregory
1

Nach 16 Stunden Erziehung und Rotationsfunktionen lol.

Ich wollte nur, dass Code ein leeres hat, das sich im Grunde genommen im Kreis dreht und sich auch von vorne dreht.

Mit anderen Worten, das Ziel ist es, Gieren und Nicken ohne Auswirkung auf das Rollen zu drehen.

Hier sind die wenigen, die in meiner Anwendung tatsächlich funktioniert haben

In der Einheit:

  1. Erstellen Sie eine leere. Nennen Sie es ForYaw.
  2. Geben Sie dem ein leeres Kind mit dem Namen ForPitch.
  3. Zum Schluss mache einen Würfel und verschiebe ihn nach z = 5 (vorwärts). Jetzt können wir sehen, was wir zu vermeiden versuchen, was den Würfel verdrehen wird (versehentliches "Rollen", während wir gieren und werfen) .

Führen Sie nun die Skripterstellung in Visual Studio durch, und führen Sie die folgenden Schritte in Update aus:

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

Beachten Sie, dass alles für mich kaputt ging, als ich versuchte, die Reihenfolge der Elternschaft zu ändern. Oder wenn ich Space.World für das untergeordnete Pitch- Objekt ändere (es macht dem übergeordneten Yaw nichts aus, durch Space.Self gedreht zu werden). Verwirrend. Ich könnte definitiv eine Erklärung gebrauchen, warum ich eine lokale Achse nehmen musste, aber sie durch den Weltraum anwenden musste - warum ruiniert Space.Self alles?

Jeh
quelle
Mir ist nicht klar, ob dies die Frage beantworten soll oder ob Sie Hilfe benötigen, um zu verstehen, warum der von Ihnen geschriebene Code auf diese Weise funktioniert. In letzterem Fall sollten Sie eine neue Frage stellen und auf diese Seite für den Kontext verweisen, wenn Sie möchten. Als Hinweis me.Rotate(me.right, angle, Space.World)ist es dasselbe wie me.Rotate(Vector3.right, angle, Space.Selfund Ihre verschachtelten Transformationen entsprechen den Beispielen "Pitch Local, Yaw Global" in der akzeptierten Antwort.
DMGregory
Ein Wenig von beidem. Teilweise um die Antwort mitzuteilen (zB hier ist genau, wie es bei mir funktioniert hat). Und auch die Frage einzuführen. Dieser Hinweis hat mich zum Nachdenken gebracht. Vielen Dank!
Jeh
Erstaunlich, dass dies nicht positiv bewertet wurde. Nach stundenlangem Lesen umfassender Antworten, die zwar lehrreich waren, aber immer noch nicht funktionierten, genügt die einfache Verwendung der Rotationsachse des Objekts anstelle einer generischen Vektor3.Rechtsachse, um die Rotation auf dem Objekt zu fixieren. Unglaublich, was ich gesucht habe, wurde hier mit 0 Stimmen begraben und auf der 2. Seite von Google von vielen, vielen ähnlichen Fragen.
User4779