Unreal Engine C++: FMatrix doc sheet
#include "Math/Matrix.h"
Memory layout
Matrix elements are accessed with:
FMatrix::M[rowIndex][columnIndex]
Therefore FMatrices
are row major in memory.
(Since: int array[2][2] = { {0, 1}, {2, 3} };
In memory looks like this: 0 1 2 3
)
Representation (CPU)
Recall that the "matrix representation" ≠ "memory layout". You can have a column-major memory layout but a row-matrix representation (basis vectors set as rows)
Ultimately it's only the user that knows how the numbers inside a FMatrices
are organized but Unreal's convention is to use a row-matrix representation:
X.x X.y X.z 0.0 // Basis vector X Y.x Y.y Y.z 0.0 // Basis vector Y Z.x Z.y Z.z 0.0 // Basis vector Z T.x T.y T.z 1.0 // Translation vector
FMatrix::setAxis()
and methods alike uses row-matrix representation.
Multiplication
Because FMatrix
convention is to use row-matrix representation the order you multiply matrices together is the opposite of the traditional math way:
-
Math (column representation):
M3 * M2 * M1 * vec
M1 gets applied first and M3 last -
UE (row representation):
vec * M1 * M2 * M3
for M1 to be applied first multiplication needs to be reversed.
Note that there is nothing particular about the implementation of the multiplication operator:
void FMatrix::mult(int A[N][N], int B[N][N]) { int C[N][N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { int num = 0; for (int k = 0; k < N; k++) { num += A[i][k] * B[k][j]; } C[i][j] = num; } } }
But since UE lays out the coefficients of the matrix in a transpose way of the conventional math way, we need to reverse the order transforms are usually multiplied.
Representation (GPU/Shader)
Something pretty usual with FMatrix
when it gets uploaded to gpu/shader in Unreal code base,
is that it gets transposed and converted to a float3x4
FMatrix44f mat; // fill mat float mat3x4_columnMajor[12]; mat.To3x4MatrixTranspose(/*out: */ mat3x4_columnMajor); // upload mat3x4_columnMajor to gpu
Therefore, usually, the code in the Unreal's shader relies on a column-matrix representation of the transformations
and multiplication happens in the conventional math way: M3 * M2 * M1 * vec
//HLSL float3x4 M1; // First transformation float3x4 M2; // Second transformation vec3 new_position = mul( M2, mul( M1, vec3(position)));
Note: HLSL memory layout is row-major, just like a FMatrix
memory layout is row-major.
If we had not transposed it, we would multiply from the left inside the Shader code as well, just like in CPU code.
Code samples
// Copy constructor / copy operator : FMatrix44f mat = FMatrix44f::Identity; mat.M[3][0] = 1.0f; mat.M[3][1] = 0.0f; mat.M[3][2] = 0.0f; FMatrix44f mat2 = mat; FMatrix44f mat3 = FMatrix44f::Identity; mat3 = mat; mat.M[3][0] = -1.0f; mat.M[3][1] = -1.0f; mat.M[3][2] = -1.0f; // FMatrix is a 4x4 *double matrix: FMatrix m = FMatrix::Identity; m.M[3][0] = -2.0; m.M[3][1] = -2.0; m.M[3][2] = -2.0; FMatrix44f mat4 = FMatrix44f(m); // Convert transform to FMatrix: FTransform tr = FTransform::Identity; mat4 = FMatrix44f( tr.ToMatrixWithScale() ); // FVector is a 3D double vector: FVector pos(1.0, 2.0, 3.0); // multiply against the 4x4 matrix (pox.x, pos.y, pos.z, 1.0) pos = m.TransformPosition( pos );
FTransform
The order of multiplication is the same as a FMatrix:
vec * T1 * T2 * T3
T1
gets applied first and T3
last.
#include "Math/Transform.h"
#include "Math/Rotator.h"
FTransform tr{ FRotator{ 10.0f, 20.0f, 30.0f }, // Rotation around each axis in degrees (y, x ,z) FVector{ 1.0f, 2.0f, 3.0f }, // Translation FVector{ 2.0f, 1.0f, 1.0f } // Scale }; FTransform tr_inv = tr.Inverse(); FTransform identity = tr * tr_inv; FVector pos = FVector{ 50.0f, 60.0f, 70.0 }; FVector new_pos = tr.TransformPosition( pos ); // WARNING: be sure to use InverseTransformPosition() and not TransformPosition() // Although each components (rotation, translation, scale) is inverted. // You must also invert the order you apply the components to truly invert the transformation: FVector back_to_pos = tr_inv.InverseTransformPosition( new_pos ); // On the other hand when working with FMatrix you don't need to // pay attention to the above corner case: FMatrix mat_inv = tr.ToMatrixWithScale().Inverse(); FVector back_to_pos = mat_inv.TransformPosition( new_pos );
No comments