Fun with Matrices
In der nächsten Teilaufgabe meines Software Renderers geht es um 3d Transformationen. Dazu verwendet man 4-dimensionale Transformationsmatrizen, wobei die zusätzliche Dimension benötigt wird um auch Translationen durch eine Multiplikation ausdrücken zu können.
Die Funktion zum Multiplizieren von zwei 4x4 Matrizen sieht so aus:
public static Mat4 mul (Mat4 A, Mat4 B) {
return new Mat4(
A.m00*B.m00 + A.m01*B.m10 + A.m02*B.m20 + A.m03*B.m30,
A.m00*B.m01 + A.m01*B.m11 + A.m02*B.m21 + A.m03*B.m31,
A.m00*B.m02 + A.m01*B.m12 + A.m02*B.m22 + A.m03*B.m32,
A.m00*B.m03 + A.m01*B.m13 + A.m02*B.m23 + A.m03*B.m33,
A.m10*B.m00 + A.m11*B.m10 + A.m12*B.m20 + A.m13*B.m30,
A.m10*B.m01 + A.m11*B.m11 + A.m12*B.m21 + A.m13*B.m31,
A.m10*B.m02 + A.m11*B.m12 + A.m12*B.m22 + A.m13*B.m32,
A.m10*B.m03 + A.m11*B.m13 + A.m12*B.m23 + A.m13*B.m33,
A.m20*B.m00 + A.m21*B.m10 + A.m22*B.m20 + A.m23*B.m30,
A.m20*B.m01 + A.m21*B.m11 + A.m22*B.m21 + A.m23*B.m31,
A.m20*B.m02 + A.m21*B.m12 + A.m22*B.m22 + A.m23*B.m32,
A.m20*B.m03 + A.m21*B.m13 + A.m22*B.m23 + A.m23*B.m33,
A.m30*B.m00 + A.m31*B.m10 + A.m32*B.m20 + A.m33*B.m30,
A.m30*B.m01 + A.m31*B.m11 + A.m32*B.m21 + A.m33*B.m31,
A.m30*B.m02 + A.m31*B.m12 + A.m32*B.m22 + A.m33*B.m32,
A.m30*B.m03 + A.m31*B.m13 + A.m32*B.m23 + A.m33*B.m33
);
}
An einer anderen Stelle habe ich die 2x2 Matrix-Multiplikation implementiert:
public static Mat2 mul (Mat2 A, Mat2 B) {
return new Mat2(
A.m00*B.m00 + A.m01*B.m10,
A.m00*B.m01 + A.m01*B.m11,
A.m10*B.m00 + A.m11*B.m10,
A.m10*B.m01 + A.m11*B.m11
);
}
Der Matrizenmultiplikation fehlen einige mathematische Eigenschaften, die man von natürlichen Zahlen her gewöhnt ist. Beispielsweise ist sie nicht kommutativ. Dafür hat sie andere recht interessante Eigenschaften, die sich gut mit DRY verbinden lassen:
Verwende ich statt der regulären Multiplikation und Addition jeweils die 2d-Matrix Varianten, kann ich meine 4x4 Matrix betrachten, als würde sie aus vier 2x2 Matrizen bestehen und es gilt
wobei , , , usw. jeweils 2x2 Matrizen repräsentieren und sich das ganze selbst wieder wie eine 2x2 Matrix verhält.
Für meinen Code bedeutet das, dass ich für meine mul
Methode in Mat4
auf Mat2.mul
zurück greifen kann und sie dadruch etwas kompakter anschreiben kann:
public static Mat4 mul (Mat4 A, Mat4 B) {
Mat2[] a = A.split();
Mat2[] b = B.split();
return new Mat4(
Mat2.add(Mat2.mul(a[0], b[0]), Mat2.mul(a[1], b[2])),
Mat2.add(Mat2.mul(a[0], b[1]), Mat2.mul(a[1], b[3])),
Mat2.add(Mat2.mul(a[2], b[0]), Mat2.mul(a[3], b[2])),
Mat2.add(Mat2.mul(a[2], b[1]), Mat2.mul(a[3], b[3]))
);
}
Die Funktion split
macht dabei nichts weiter als die 4x4 Matrix in 2x2er zu unterteilen. (Sinnvoll wäre es die Werte der 4x4 Matrix von vornherein in Mat2
Objekten zu speichern, was in meinem Fall aber nicht problemlos möglich war)