Hallo allerseits! Mein Name ist Grisha und ich bin der Gründer von CGDevs. Heute möchte ich das Thema Mathematik in der Spieleentwicklung fortsetzen. In einem
früheren Artikel haben wir grundlegende Beispiele für die Verwendung von Vektoren und Integralen in Unity-Projekten gezeigt. Lassen Sie uns nun über Matrizen und affine Transformationen sprechen. Wenn Sie sich mit Matrixarithmetik auskennen; Sie wissen, was TRS ist und wie Sie damit arbeiten können. Was ist die Householder-Transformation? Sie werden wahrscheinlich nichts Neues für sich finden. Wir werden im Zusammenhang mit 3D-Grafiken sprechen. Wenn Sie sich für dieses Thema interessieren - willkommen bei cat.

Beginnen wir mit einem der wichtigsten Konzepte im Kontext des Artikels -
affine Transformationen .
Affine Transformationen sind im Wesentlichen die Transformation des Koordinatensystems (oder des Raums) durch Multiplikation des Vektors mit einer speziellen Matrix. Zum Beispiel Transformationen wie Bewegung, Rotation, Skalierung, Reflexion usw. Die Haupteigenschaften affiner Transformationen sind, dass Sie im selben Raum bleiben (es ist unmöglich, einen zweidimensionalen Vektor aus zweidimensional zu erstellen) und dass die Linien sich schneiden / parallel sind Wurde vor der Konvertierung gekreuzt, bleibt diese Eigenschaft nach der Konvertierung erhalten. Darüber hinaus verfügen sie über viele mathematische Eigenschaften, die Kenntnisse der Theorie von Gruppen, Mengen und linearer Algebra erfordern, was die Arbeit mit ihnen erleichtert.
TRS-Matrix
Das zweite wichtige Konzept in der Computergrafik ist die
TRS-Matrix . Mithilfe dieser Funktion können Sie die häufigsten Vorgänge beschreiben, die beim Arbeiten mit Computergrafiken verwendet werden.
Eine TRS-Matrix besteht aus drei Transformationsmatrizen. Verschiebungsmatrizen (Translation), Rotation um jede Achse (Rotation) und Skalierung (Scale).
Sie sieht so aus.

Wo:
Die Bewegung ist
t = neuer Vektor3 (d, h, l).
Skalierung -
s = neuer Vektor3 (neuer Vektor3 (a, e, i) .Magnitude, neuer Vector3 (b, f, j) .Magnitude, neuer Vector3 (c, g, k) .Magnitude);
Eine Rotation ist eine Matrix der Form:

Lassen Sie uns nun etwas tiefer in den Kontext der Einheit gehen. Zunächst ist die TRS-Matrix eine sehr praktische Sache, sollte aber nicht überall verwendet werden. Da eine einfache Anzeige der Position oder Addition von Vektoren in einer Einheit schneller funktioniert, sind Matrizen in vielen mathematischen Algorithmen um ein Vielfaches bequemer als Vektoren. Die TRS-Funktionalität in Unity ist weitgehend in der
Matrix4x4- Klasse implementiert, aus Sicht der Anwendung jedoch nicht praktisch. Da die Matrix nicht nur durch Multiplikation angewendet werden kann, sondern im Allgemeinen auch Informationen zur Ausrichtung des Objekts speichern kann, möchte ich für einige Transformationen nicht nur die Position berechnen, sondern auch die Ausrichtung des gesamten Objekts ändern können (z. B. Reflexion, die nicht in Unity implementiert ist).
Alle Beispiele für das lokale Koordinatensystem sind nachstehend aufgeführt (der Ursprung des GameObject wird als Ursprung der Koordinaten betrachtet, innerhalb derer sich das Objekt befindet. Wenn das Objekt die Wurzel der Hierarchie in der Einheit ist, ist der Ursprung die Welt (0,0,0)).
Da wir mit der TRS-Matrix grundsätzlich die Position eines Objekts im Raum beschreiben können, benötigen wir eine Zerlegung von TRS zu den spezifischen Werten für Position, Rotation und Skalierung für Unity. Dazu können Sie Erweiterungsmethoden für die Matrix4x4-Klasse schreiben
Position, Drehung und Skalierung ermittelnpublic static Vector3 ExtractPosition(this Matrix4x4 matrix) { Vector3 position; position.x = matrix.m03; position.y = matrix.m13; position.z = matrix.m23; return position; } public static Quaternion ExtractRotation(this Matrix4x4 matrix) { Vector3 forward; forward.x = matrix.m02; forward.y = matrix.m12; forward.z = matrix.m22; Vector3 upwards; upwards.x = matrix.m01; upwards.y = matrix.m11; upwards.z = matrix.m21; return Quaternion.LookRotation(forward, upwards); } public static Vector3 ExtractScale(this Matrix4x4 matrix) { Vector3 scale; scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude; scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude; scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude; return scale; }
Darüber hinaus können Sie für eine bequeme Arbeit einige Erweiterungen der Transform-Klasse implementieren, um mit TRS darin zu arbeiten.
Expansionstransformation public static void ApplyLocalTRS(this Transform tr, Matrix4x4 trs) { tr.localPosition = trs.ExtractPosition(); tr.localRotation = trs.ExtractRotation(); tr.localScale = trs.ExtractScale(); } public static Matrix4x4 ExtractLocalTRS(this Transform tr) { return Matrix4x4.TRS(tr.localPosition, tr.localRotation, tr.localScale); }
Damit enden die Vorteile der Einheit, da die Matrizen in der Einheit im Betrieb sehr schlecht sind. Viele Algorithmen erfordern eine Matrixarithmetik, die selbst in vollständig einfachen Operationen wie dem Hinzufügen von Matrizen und dem Multiplizieren von Matrizen mit einem Skalar nicht in der Einheit implementiert ist. Aufgrund der Besonderheit der Implementierung von Vektoren in Unity3d gibt es außerdem eine Reihe von Unannehmlichkeiten, die mit der Tatsache verbunden sind, dass Sie einen 4x4-Vektor erstellen können, aber nicht sofort 1x4 erstellen können. Da wir mehr über die Transformation des Haushaltsinhabers für Überlegungen sprechen werden, implementieren wir zunächst die dafür erforderlichen Operationen.
Das Addieren / Subtrahieren und Multiplizieren mit einem Skalar ist einfach. Es sieht ziemlich sperrig aus, aber hier gibt es nichts Kompliziertes, da die Arithmetik einfach ist.
Grundlegende Matrixoperationen public static Matrix4x4 MutiplyByNumber(this Matrix4x4 matrix, float number) { return new Matrix4x4( new Vector4(matrix.m00 * number, matrix.m10 * number, matrix.m20 * number, matrix.m30 * number), new Vector4(matrix.m01 * number, matrix.m11 * number, matrix.m21 * number, matrix.m31 * number), new Vector4(matrix.m02 * number, matrix.m12 * number, matrix.m22 * number, matrix.m32 * number), new Vector4(matrix.m03 * number, matrix.m13 * number, matrix.m23 * number, matrix.m33 * number) ); } public static Matrix4x4 DivideByNumber(this Matrix4x4 matrix, float number) { return new Matrix4x4( new Vector4(matrix.m00 / number, matrix.m10 / number, matrix.m20 / number, matrix.m30 / number), new Vector4(matrix.m01 / number, matrix.m11 / number, matrix.m21 / number, matrix.m31 / number), new Vector4(matrix.m02 / number, matrix.m12 / number, matrix.m22 / number, matrix.m32 / number), new Vector4(matrix.m03 / number, matrix.m13 / number, matrix.m23 / number, matrix.m33 / number) ); } public static Matrix4x4 Plus(this Matrix4x4 matrix, Matrix4x4 matrixToAdding) { return new Matrix4x4( new Vector4(matrix.m00 + matrixToAdding.m00, matrix.m10 + matrixToAdding.m10, matrix.m20 + matrixToAdding.m20, matrix.m30 + matrixToAdding.m30), new Vector4(matrix.m01 + matrixToAdding.m01, matrix.m11 + matrixToAdding.m11, matrix.m21 + matrixToAdding.m21, matrix.m31 + matrixToAdding.m31), new Vector4(matrix.m02 + matrixToAdding.m02, matrix.m12 + matrixToAdding.m12, matrix.m22 + matrixToAdding.m22, matrix.m32 + matrixToAdding.m32), new Vector4(matrix.m03 + matrixToAdding.m03, matrix.m13 + matrixToAdding.m13, matrix.m23 + matrixToAdding.m23, matrix.m33 + matrixToAdding.m33) ); } public static Matrix4x4 Minus(this Matrix4x4 matrix, Matrix4x4 matrixToMinus) { return new Matrix4x4( new Vector4(matrix.m00 - matrixToMinus.m00, matrix.m10 - matrixToMinus.m10, matrix.m20 - matrixToMinus.m20, matrix.m30 - matrixToMinus.m30), new Vector4(matrix.m01 - matrixToMinus.m01, matrix.m11 - matrixToMinus.m11, matrix.m21 - matrixToMinus.m21, matrix.m31 - matrixToMinus.m31), new Vector4(matrix.m02 - matrixToMinus.m02, matrix.m12 - matrixToMinus.m12, matrix.m22 - matrixToMinus.m22, matrix.m32 - matrixToMinus.m32), new Vector4(matrix.m03 - matrixToMinus.m03, matrix.m13 - matrixToMinus.m13, matrix.m23 - matrixToMinus.m23, matrix.m33 - matrixToMinus.m33) ); }
Zur Reflexion benötigen wir jedoch die Operation der Matrixmultiplikation in einem bestimmten Fall. Multiplikation eines Vektors der Dimension 4x4 mit 1x4 (transponiert) Wenn Sie mit der Matrixmathematik vertraut sind, wissen Sie, dass Sie bei dieser Multiplikation die extremen Zahlen der Dimension betrachten müssen, und Sie erhalten die Dimension der Matrix am Ausgang, dh in diesem Fall 4x4. Es gibt genügend Informationen darüber, wie Matrizen multipliziert werden, daher werden wir dies nicht malen. Hier ist ein Beispiel für einen bestimmten Fall, der sich in Zukunft als nützlich erweisen wird.
Multiplizieren Sie einen Vektor mit einem transponierten public static Matrix4x4 MultiplyVectorsTransposed(Vector4 vector, Vector4 transposeVector) { float[] vectorPoints = new[] {vector.x, vector.y, vector.z, vector.w}, transposedVectorPoints = new[] {transposeVector.x, transposeVector.y, transposeVector.z, transposeVector.w}; int matrixDimension = vectorPoints.Length; float[] values = new float[matrixDimension * matrixDimension]; for (int i = 0; i < matrixDimension; i++) { for (int j = 0; j < matrixDimension; j++) { values[i + j * matrixDimension] = vectorPoints[i] * transposedVectorPoints[j]; } } return new Matrix4x4( new Vector4(values[0], values[1], values[2], values[3]), new Vector4(values[4], values[5], values[6], values[7]), new Vector4(values[8], values[9], values[10], values[11]), new Vector4(values[12], values[13], values[14], values[15]) ); }
Transformation der Haushalte
Auf der Suche nach der Reflektion eines Objekts relativ zu einer Achse stoße ich häufig auf Ratschläge, eine negative Skala in die erforderliche Richtung zu setzen. Dies ist im Kontext von Unity ein sehr schlechter Rat, da viele Systeme in der Engine beschädigt werden (Butching, Kollisionen usw.). In einigen Algorithmen wird dies zu nicht trivialen Berechnungen, wenn Sie in Bezug auf Vector3.up oder Vector3.forward nicht trivial reflektieren müssen. aber in eine beliebige Richtung. Die Reflexionsmethode in einer Einheit außerhalb der Box selbst ist nicht implementiert, daher habe ich
die Householder-Methode implementiert.
Die Householder-Transformation wird nicht nur in der Computergrafik verwendet, sondern ist in diesem Zusammenhang eine lineare Transformation, die ein Objekt relativ zu einer Ebene widerspiegelt, die durch den „Ursprung“ verläuft und durch die Normale zur Ebene bestimmt wird. In vielen Quellen wird es ziemlich kompliziert und unverständlich beschrieben, obwohl seine Formel elementar ist.
H = I-2 * n * (n ^ T)Wenn
H die Transformationsmatrix ist, ist
I in unserem Fall Matrix4x4.identity und
n = neuer Vektor4 (planeNormal.x, planeNormal.y, planeNormal.z, 0). Das Symbol T bedeutet Transposition, dh nach Multiplikation von
n * (n ^ T) erhalten wir eine 4x4-Matrix.
Die implementierten Methoden werden sich als nützlich erweisen und die Aufnahme wird sehr kompakt sein.
Transformation der Haushalte public static Matrix4x4 HouseholderReflection(this Matrix4x4 matrix4X4, Vector3 planeNormal) { planeNormal.Normalize(); Vector4 planeNormal4 = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, 0); Matrix4x4 householderMatrix = Matrix4x4.identity.Minus( MultiplyVectorsTransposed(planeNormal4, planeNormal4).MutiplyByNumber(2)); return householderMatrix * matrix4X4; }
Wichtig: planeNormal sollte normalisiert werden (was logisch ist) und die letzte Koordinate n sollte 0 sein, damit sich die Dehnung in Richtung nicht auswirkt, da dies von der Länge des Vektors n abhängt.
Zur Vereinfachung in Unity implementieren wir jetzt die Erweiterungsmethode für die Transformation
Reflexion der Transformation im lokalen Koordinatensystem public static void LocalReflect(this Transform tr, Vector3 planeNormal) { var trs = tr.ExtractLocalTRS(); var reflected = trs.HouseholderReflection(planeNormal); tr.ApplyLocalTRS(reflected); }
Das ist alles für heute, wenn diese Artikelserie weiterhin interessant sein wird, werde ich auch andere Anwendungen der Mathematik in der Spieleentwicklung aufzeigen. Dieses Mal wird es kein Projekt geben, da der gesamte Code im Artikel platziert ist, aber das Projekt mit der spezifischen Anwendung wird im nächsten Artikel enthalten sein. Auf dem Bild können Sie erraten, worum es im nächsten Artikel geht.

Vielen Dank für Ihre Aufmerksamkeit!