Eintrag
Es scheint mir, dass wir in der Computergrafik weniger Trigonometrie verwenden müssen. Ein gutes Verständnis von Projektionen, Reflexionen und Vektoroperationen (wie in der wahren Bedeutung von Skalar- (Punkt) und Vektor- (Kreuz-) Produkten von Vektoren) führt normalerweise zu einem wachsenden Gefühl der Angst bei der Verwendung der Trigonometrie. Genauer gesagt glaube ich, dass Trigonometrie gut für die Eingabe von Daten in den Algorithmus ist (für das Konzept der Winkel ist dies eine intuitive Methode zur Messung der Orientierung). Ich habe das Gefühl, dass etwas nicht stimmt, wenn ich Trigonometrie in den Tiefen eines 3D-Rendering-Algorithmus sehe. Tatsächlich denke ich, dass irgendwo ein Kätzchen stirbt, wenn sich dort Trigonometrie einschleicht. Und ich mache mir keine Sorgen um Geschwindigkeit oder Genauigkeit, aber mit konzeptioneller Eleganz denke ich ... Jetzt werde ich es erklären.
An anderen Stellen habe ich bereits diskutiert, dass Skalar- und Vektorprodukte von Vektoren alle notwendigen Informationen für Rotationen enthalten, für diese beiden „rechteckigen“ Operationen - Sinus und Cosinus von Winkeln. Diese Informationen entsprechen Sinus und Cosinus an einer so großen Anzahl von Stellen, dass es den Anschein hat, als könnten Sie einfach das Produkt von Vektoren verwenden und Trigonometrie und Winkel loswerden. In der Praxis können Sie dies tun, indem Sie in gewöhnlichen euklidischen Vektoren ohne Trigonometrie bleiben. Das lässt uns fragen: "Tun wir nicht etwas Überflüssiges?" Scheint zu tun. Leider neigen selbst erfahrene Fachleute dazu, Trigonometrie zu missbrauchen und die Dinge sehr komplex, umständlich und nicht besonders präzise zu gestalten. Und möglicherweise sogar "falsch".
Hören wir auf, den Artikel noch abstrakter zu gestalten. Stellen wir uns einen der Fälle vor, in denen trigonometrische Formeln durch Vektorprodukte ersetzt werden, und sehen wir, worüber ich gerade gesprochen habe.
Falsche Option zum Drehen eines Raums oder Objekts
Lassen Sie uns eine Funktion haben, die die Rotationsmatrix eines Vektors um einen normalisierten Vektor berechnet
an der Ecke
. In jeder 3D-Engine oder Echtzeit-Mathematikbibliothek gibt es eine solche Funktion, die höchstwahrscheinlich blind aus einer anderen Engine, einem Wikipedia- oder OpenGL-Tutorial kopiert wird ... (Ja, zu diesem Zeitpunkt müssen Sie zugeben, und je nach Stimmung können Sie sich Sorgen machen aus diesem Grund).
Die Funktion sieht ungefähr so aus:
mat3x3 rotationAxisAngle( const vec3 & v, float a ) { const float si = sinf( a ); const float co = cosf( a ); const float ic = 1.0f - co; return mat3x3( vx*vx*ic + co, vy*vx*ic - si*vz, vz*vx*ic + si*vy, vx*vy*ic + si*vz, vy*vy*ic + co, vz*vy*ic - si*vx, vx*vz*ic - si*vy, vy*vz*ic + si*vx, vz*vz*ic + co ); }
Stellen Sie sich vor, Sie graben sich durch die Innenseiten einer Demo oder eines Spiels und beenden möglicherweise eine Art Animationsmodul. Sie müssen das Objekt in eine bestimmte Richtung drehen. Sie möchten es so drehen, dass eine seiner Achsen beispielsweise eine Achse ist
fiel mit einem bestimmten Vektor zusammen
Tangente an den Animationspfad. Sie entscheiden sich natürlich dafür, mithilfe von
rotationAxisAngle()
eine Matrix zu erstellen, die Transformationen enthält. Sie müssen also zuerst den Winkel zwischen den Achsen messen
Ihr Objekt und den gewünschten Orientierungsvektor. Da Sie ein Grafikprogrammierer sind, wissen Sie, dass dies mit einem Skalarprodukt und anschließendem Extrahieren des Winkels mit
acos()
.
Sie wissen auch, dass
acosf()
manchmal seltsame Werte zurückgeben kann, wenn das Skalarprodukt außerhalb des Bereichs [-1; 1], und Sie beschließen, seinen Wert so zu ändern, dass er in diesen Bereich fällt (
ca. Per. Zu klemmen) (an dieser Stelle können Sie sogar die Genauigkeit Ihres Computers beschuldigen, da die Länge des normalisierten Vektors nicht genau 1 beträgt). Zu diesem Zeitpunkt starb ein Kätzchen. Aber bis Sie davon wissen, schreiben Sie Ihren Code weiter. Als nächstes berechnen Sie die Rotationsachse und wissen, dass dies ein Vektorprodukt eines Vektors ist
Ihr Objekt und die gewählte Richtung
Alle Punkte in Ihrem Objekt drehen sich in Ebenen parallel zu den durch diese beiden Vektoren definierten, nur für den Fall ... (das Kätzchen wurde wiederbelebt und erneut getötet). Infolgedessen sieht der Code ungefähr so aus:
const vec3 axi = normalize( cross( z, d ) ); const float ang = acosf( clamp( dot( z, d ), -1.0f, 1.0f ) ); const mat3x3 rot = rotationAxisAngle( axi, ang );
Um zu verstehen, warum dies funktioniert, aber immer noch fälschlicherweise, werden wir den gesamten Code von
rotationAxisAngle()
und sehen, was wirklich passiert:
const vec3 axi = normalize( cross( z, d ) ); const float ang = acosf( clamp( dot( z, d ), -1.0f, 1.0f ) ); const float co = cosf( ang ); const float si = sinf( ang ); const float ic = 1.0f - co; const mat3x3 rot = mat3x3( axi.x*axi.x*ic + co, axi.y*axi.x*ic - si*axi.z, axi.z*axi.x*ic + si*axi.y, axi.x*axi.y*ic + si*axi.z, axi.y*axi.y*ic + co, axi.z*axi.y*ic - si*axi.x, axi.x*axi.z*ic - si*axi.y, axi.y*axi.z*ic + si*axi.x, axi.z*axi.z*ic + co);
Wie Sie vielleicht bemerkt haben, führen wir einen ziemlich ungenauen und teuren Acos-Anruf durch, um ihn sofort abzubrechen, indem wir den Kosinus des Rückgabewerts berechnen. Und die erste Frage erscheint: "Warum nicht die
acos()
--->
cos()
überspringen und CPU-Zeit sparen?" Sagt uns das nicht darüber hinaus, dass wir etwas falsch und sehr kompliziert machen und dass ein einfaches mathematisches Prinzip zu uns kommt, das sich durch die Vereinfachung dieses Ausdrucks manifestiert?
Sie können argumentieren, dass eine Vereinfachung nicht möglich ist, da Sie einen Winkel benötigen, um den Sinus zu berechnen. Dies ist jedoch nicht so. Wenn Sie mit dem Vektorprodukt von Vektoren vertraut sind, wissen Sie, dass genau wie das Skalarprodukt Cosinus enthält, der Vektor Sinus enthält. Die meisten Grafikprogrammierer verstehen, warum ein Skalarprodukt von Vektoren benötigt wird, aber nicht jeder versteht, warum ein Vektorprodukt benötigt wird (und verwendet es nur zum Lesen von Normalen und Rotationsachsen). Grundsätzlich sagt uns das mathematische Prinzip, das uns hilft, das cos / acos-Paar loszuwerden, dass es dort, wo es ein Skalarprodukt gibt, möglicherweise ein Vektorprodukt gibt, das die fehlende Information meldet (senkrechter Teil, Sinus).
Der richtige Weg, um einen Raum oder ein Objekt zu drehen
Jetzt können wir den Sinus des Winkels zwischen extrahieren
und
nur indem Sie die Länge ihres Vektorprodukts betrachten ... - denken Sie daran
und
normalisiert! Und das bedeutet, dass wir die Funktion folgendermaßen umschreiben können (wir müssen !!):
const vec3 axi = cross( z, d ); const float si = length( axi ); const float co = dot( z, d ); const mat3x3 rot = rotationAxisCosSin( axi/si, co, si );
und stellen Sie sicher, dass unsere neue Rotationsmatrix-Konstruktionsfunktion ,
rotationAxisCosSin()
, nirgendwo Sinus und Cosinus berechnet, sondern als Argumente verwendet:
mat3x3 rotationAxisCosSin( const vec3 & v, const float co, const float si ) { const float ic = 1.0f - co; return mat3x3( vx*vx*ic + co, vy*vx*ic - si*vz, vz*vx*ic + si*vy, vx*vy*ic + si*vz, vy*vy*ic + co, vz*vy*ic - si*vx, vx*vz*ic - si*vy, vy*vz*ic + si*vx, vz*vz*ic + co ); }
Es gibt noch eine Möglichkeit, Normalisierungen und Quadratwurzeln zu beseitigen: die gesamte Logik in einer neuen Funktion zu kapseln und
1/si
an die Matrix zu übergeben:
mat3x3 rotationAlign( const vec3 & d, const vec3 & z ) { const vec3 v = cross( z, d ); const float c = dot( z, d ); const float k = (1.0fc)/(1.0fc*c); return mat3x3( vx*vx*k + c, vy*vx*k - vz, vz*vx*k + vy, vx*vy*k + vz, vy*vy*k + c, vz*vy*k - vx, vx*vz*K - vy, vy*vz*k + vx, vz*vz*k + c ); }
Später bemerkte Zoltan Vrana, dass
k
auf
k = 1/(1+c)
vereinfacht werden kann, was nicht nur mathematisch eleganter aussieht, sondern auch zwei Merkmale nach k und damit die gesamte Funktion verschiebt (
und
parallel) geht in eins (wenn
und
fallen in diesem Fall keine deutliche Drehung zusammen). Der endgültige Code sieht ungefähr so aus:
mat3x3 rotationAlign( const vec3 & d, const vec3 & z ) { const vec3 v = cross( z, d ); const float c = dot( z, d ); const float k = 1.0f/(1.0f+c); return mat3x3( vx*vx*k + c, vy*vx*k - vz, vz*vx*k + vy, vx*vy*k + vz, vy*vy*k + c, vz*vy*k - vx, vx*vz*K - vy, vy*vz*k + vx, vz*vz*k + c ); }
Wir haben nicht nur drei trigonometrische Funktionen und die hässliche Klammer (und Normalisierung!) Entfernt, sondern auch unsere 3D-Mathematik konzeptionell vereinfacht. Keine transzendentalen Funktionen, hier werden nur Vektoren verwendet. Vektoren erstellen Matrizen, die andere Vektoren modifizieren. Und das ist wichtig, denn je weniger Trigonometrie in Ihrer 3D-Engine vorhanden ist, desto schneller und klarer wird sie, sondern vor allem mathematisch eleganter (korrekter!).