# Model View Projection Matrix Multiplcation Order

I'm working on a simple software renderer and have a working implementation so far. I'm curious as to why it's actually working since I would expect the multiplication ordering for my world, view and projection matrices to be reversed. When working with DirectX and OpenGL in the past I have multiplied them as projection * view * world, but with my implementation it's working with worldMatrix * viewMatrix * projectionMatrix. Some code:

void SceneRenderer::DrawMesh(Mesh& mesh, const SRGraphicsContext& gfx)
{
const Mat4x4f& worldMatrix = mesh.GetWorldMatrix();
const Mat4x4f& viewMatrix = camera->GetViewMatrix();
const Mat4x4f& projectionMatrix = camera->GetProjectionMatrix();
const Mat4x4f& wvp = worldMatrix * viewMatrix * projectionMatrix;

const Vert3df& cameraPos = camera->GetPosition();

const float screenWidth = static_cast<float>(gfx.frameBuffer->GetWidth());
const float screenHeight = static_cast<float>(gfx.frameBuffer->GetHeight());

Tri2di rasterTri;
for (Tri3df& tri : mesh.GetTriangles())
{
Vert4df p1c =  Vert4df(tri.p1, 1),
p2c = Vert4df(tri.p2, 1),
p3c = Vert4df(tri.p3, 1);

p1c = wvp * p1c;
p2c = wvp * p2c;
p3c = wvp * p3c;

p1c.DivideByW();
p2c.DivideByW();
p3c.DivideByW();

rasterTri.p1.x = static_cast<int>((p1c.x + 1.0f) / 2.0f * screenWidth);
rasterTri.p1.y = static_cast<int>((p1c.y + 1.0f) / 2.0f * screenHeight);

rasterTri.p2.x = static_cast<int>((p2c.x + 1.0f) / 2.0f * screenWidth);
rasterTri.p2.y = static_cast<int>((p2c.y + 1.0f) / 2.0f * screenHeight);

rasterTri.p3.x = static_cast<int>((p3c.x + 1.0f) / 2.0f * screenWidth);
rasterTri.p3.y = static_cast<int>((p3c.y + 1.0f) / 2.0f * screenHeight);

rasterizer->DrawTriangle(rasterTri, gfx);
}

void Camera::LookAt(const Vert3df& at)
{
lookAt = at;

auto zAxis = (lookAt - position).Normalize();
auto xAxis = up.Cross(zAxis).Normalize();
auto yAxis = zAxis.Cross(xAxis);

viewMatrix.Zero();
viewMatrix(0, 0) = xAxis.x;
viewMatrix(0, 1) = yAxis.x;
viewMatrix(0, 2) = zAxis.x;
viewMatrix(0, 3) = 0.0f;
viewMatrix(1, 0) = xAxis.y;
viewMatrix(1, 1) = yAxis.y;
viewMatrix(1, 2) = zAxis.y;
viewMatrix(1, 3) = 0.0f;
viewMatrix(2, 0) = xAxis.z;
viewMatrix(2, 1) = yAxis.z;
viewMatrix(2, 2) = zAxis.z;
viewMatrix(2, 3) = 0.0f;
viewMatrix(3, 0) = -xAxis.Dot(position);
viewMatrix(3, 1) = -yAxis.Dot(position);
viewMatrix(3, 2) = -zAxis.Dot(position);
viewMatrix(3, 3) = 1.0f;
}

void Camera::UpdateProjectionMatrix()
{
const float aspectRatio = screenWidth / (float)screenHeight;

float tanHalfFOV = std::tan(fov / 2.0f);
float zRange = nearPlane - farPlane;

projectionMatrix.Zero();
projectionMatrix(0, 0) = 1.0f / (tanHalfFOV * aspectRatio);
projectionMatrix(1, 1) = 1.0f / tanHalfFOV;
projectionMatrix(2, 2) = (-nearPlane - farPlane) / zRange;
projectionMatrix(2, 3) = 1; // Left handed, invert for right handed
projectionMatrix(3, 2) = (2 * nearPlane * farPlane) / zRange;
}


Any idea why the ordering might be reversed in my case? I've compared my Mat4x4f implementation to DirectX's XMMAtrix and get the same results when multiplying 2 arbitrary matrices.

• if you can compare check the result of pvw * p1 in DirectX with the same values. If its same as your wvp * p1 then your implementation is opposite. If it's not the same, then your implementation might be fine and the problem might be somewhere else I guess. Dec 1 '20 at 10:53
• In matrix mathematics transposing the matrix reverses the calculation order. So $A * B = ( B^T * A^T)^T$ Dec 1 '20 at 12:55
• I saw a mention of that in another thread, but I don't believe I'm transposing the matrices anywhere. Does DirectX or OpenGl implicitly transpose these matrices somewhere? Dec 2 '20 at 3:22
• You dont have to transpose a matrix to get the effect. All you have to do is build the matrix differently with different assumptions. A column major build order results in a matrix that is seeminly transposed version of a row major matrix. So all that needs to happen is that your not aware of the convention used. Buth are mathematically equivalent and as correct. I mean you could also have a convention where the translate part is first, or second and not last like is customary. But no one does that. But its entirely calid to use different building rule Dec 4 '20 at 16:34
• I mean its entirely valid to think from world to object than it is from object to world. Mathematicians prefer inside out because it follows the logic of function composition but you dont have to its natural for humans to think of other objects from outside in. Dec 4 '20 at 16:39