1
$\begingroup$

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.

$\endgroup$
6
  • $\begingroup$ 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. $\endgroup$ Commented Dec 1, 2020 at 10:53
  • 1
    $\begingroup$ In matrix mathematics transposing the matrix reverses the calculation order. So $A * B = ( B^T * A^T)^T$ $\endgroup$
    – joojaa
    Commented Dec 1, 2020 at 12:55
  • $\begingroup$ 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? $\endgroup$ Commented Dec 2, 2020 at 3:22
  • $\begingroup$ 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 $\endgroup$
    – joojaa
    Commented Dec 4, 2020 at 16:34
  • $\begingroup$ 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. $\endgroup$
    – joojaa
    Commented Dec 4, 2020 at 16:39

0