Search code examples
c++matrixcameratransformationglm-math

Scene rendering wonky when camera transformations occur


I've recently switched over to GLM for managing my matrices and vectors, however when I change my variables such as camera angles or position, the whole rendered scene goes haywire.

I really don't know how to describe it other than stretching and moving all over the place.

Problem:

  • "Camera" transformations such as panning the camera result in strange atypical/unexpected changes. Typically, when the camera pan variables like X and Y deviate from "0"

Note:

  • I used to perform these very same types of transformations on Qt's datatypes for QMatrix4x4 and QVector3D, rather than glm::mat4x4 and glm::vec4, and it worked fine

Here is the way I'm implementing the camera in my render function (alpha and beta are rotation vars, and = 0 by default, camX and camY are panning vars, and also = 0 by default):

glm::mat4x4 mMatrix;
glm::mat4x4 vMatrix;

glm::mat4x4 cameraTransformation;
cameraTransformation = glm::rotate(cameraTransformation, glm::radians(alpha)/*alpha*(float)M_PI/180*/, glm::vec3(0, 1 ,0));
cameraTransformation = glm::rotate(cameraTransformation, glm::radians(beta)/*beta*(float)M_PI/180*/, glm::vec3(1, 0, 0));

glm::vec4 cameraPosition = (cameraTransformation * glm::vec4(camX, camY, distance, 0));
glm::vec4 cameraUpDirection = cameraTransformation * glm::vec4(0, 1, 0, 0);

vMatrix = glm::lookAt(glm::vec3(cameraPosition[0],cameraPosition[1],cameraPosition[2]), glm::vec3(camX, camY, 0.0), glm::vec3(cameraUpDirection[0],cameraUpDirection[1],cameraUpDirection[2]));

glm::mat4x4 glmat = pMatrix * vMatrix * mMatrix;
QMatrix4x4 qmat = QMatrix4x4(glmat[0][0],glmat[0][1],glmat[0][2],glmat[0][3],
                             glmat[1][0],glmat[1][1],glmat[1][2],glmat[1][3],
                             glmat[2][0],glmat[2][1],glmat[2][2],glmat[2][3],
                             glmat[3][0],glmat[3][1],glmat[3][2],glmat[3][3]);
shaderProgram.bind();
shaderProgram.setUniformValue("mvpMatrix", qmat);

I set up my projection matrix as so (fov = 30 degrees):

pMatrix = glm::perspective( glm::radians(fov), (float)width/(float)height, (float)0.001, (float)10000 );

My matrices look like this at the time they are used: matrices

Here's an example of how it looks

Before any changes, all values are at 0: example1

When camX changes to 14 (note, I didn't rotate my camera around!): example2


Solution

  • glm::mat4x4 cameraTransformation;
    cameraTransformation = glm::rotate(cameraTransformation, glm::radians(alpha)/*alpha*(float)M_PI/180*/, glm::vec3(0, 1 ,0));
    cameraTransformation = glm::rotate(cameraTransformation, glm::radians(beta)/*beta*(float)M_PI/180*/, glm::vec3(1, 0, 0));
    

    This can be simplified by using matrix multiplication and using a different glm call:

    glm::mat4x4 cameraTransformation =
    glm::rotate(glm::radians(alpha), glm::vec3(0,1,0)) *
    glm::rotate(glm::radians(beta), glm::vec3(1,0,0));
    

    Next:

    glm::vec4 cameraPosition = (cameraTransformation * glm::vec4(camX, camY, distance, 0));
    glm::vec4 cameraUpDirection = cameraTransformation * glm::vec4(0, 1, 0, 0);
    

    Having a zero in the w component of a vector indicates that the vector is a direction, not a position. Yet you are obtaining a position vector as the output. This happens to work because cameraTransformation has only rotation operations, not translating operations, but it's better to be clear:

    glm::vec3 cameraPosition = glm::vec3(cameraTransformation * glm::vec4(camX, camY, distance, 1));
    

    Note- I use a vec3 not a vec4 because I just like to do that.

    For the next part you actually do want a direction vector and not a position vector, so you should have a zero in the w component. Still cast it to a vec3, because it's just clearer in my opinion.

    glm::vec3 cameraUpDirection = glm::vec3(cameraTransformation * glm::vec4(0, 1, 0, 0));
    

    Next:

    vMatrix=
    glm::lookAt(glm::vec3(cameraPosition[0],cameraPosition[1],cameraPosition[2]),
    glm::vec3(camX, camY, 0.0),
    glm::vec3(cameraUpDirection[0],cameraUpDirection[1],cameraUpDirection[2]));
    

    Glm lets you pass a vec3 into a vec4 as a constructor parameter so you can shorten your code like this:

    vMatrix=
    glm::lookAt(glm::vec3(cameraPosition),
    glm::vec3(camX, camY, 0.0),
    glm::vec3(cameraUpDirection));
    

    But we don't even need to do that because i changed the variables into vec3s not vec4s:

    vMatrix= glm::lookAt(cameraPosition, glm::vec3(camX, camY, 0.0), cameraUpDirection);
    

    And finally, you can access the components of a glm vector using .x,.y,.z,.w instead of the [] operator, which I imagine is much safer and easier to read.