4
\$\begingroup\$

I'm working on a ray-tracer in C++ with an adjustable camera for a university assignment. I'm entirely new with graphics and I'm struggling with one thing.

I have a matrix as a mat4 object (4x4 matrix with each cell saved in a 1D array) that I save in a Camera object that handles transformations of every point in the scene (which is currently just two spheres). Translations seem to be working fine, but when I attempt rotations, things break. The spheres deform, and it only gets worse the more I attempt to rotate the camera.

This is where I set the camera matrix:

mat4 Renderer::setCameraMatrix(const Camera &camera)
{
    mat4 cameraToWorld = mat4(); // Initialise 4x4 matrix.

    // Translation.
    cameraToWorld.cell[3] = camera.pos.x;
    cameraToWorld.cell[7] = camera.pos.y;
    cameraToWorld.cell[11] = camera.pos.z;

    // Rotation.
    cameraToWorld.cell[0] = camera.orientmat.cell[0], cameraToWorld.cell[1] = camera.orientmat.cell[1], cameraToWorld.cell[2] = camera.orientmat.cell[2];
    cameraToWorld.cell[4] = camera.orientmat.cell[4], cameraToWorld.cell[5] = camera.orientmat.cell[5], cameraToWorld.cell[6] = camera.orientmat.cell[6];
    cameraToWorld.cell[8] = camera.orientmat.cell[8], cameraToWorld.cell[9] = camera.orientmat.cell[9], cameraToWorld.cell[10] = camera.orientmat.cell[10];

    return cameraToWorld;
}

This matrix is then used to set the origin and direction of the camera in the world space later on in the code:

vec3 orig = transformpoint(vec3(0), cameraToWorld);
/*...*/
vec3 dir = transformvector(vec3(x, y, -1), cameraToWorld) - orig;
dir.normalize();

The transformpoint and transformvector functions basically multiply a vector v by a matrix M (M.v), except the former takes into consideration translations, and the latter doesn't. Their code can be found here.

The orientmat matrix is calculated from the three separate orientation angles (orient.x, orient.y, orient.z) in this line of code:

cam->orientmat = mat4().rotatexyz(cam->orient.x, cam->orient.y, cam->orient.z);

Using this function:

mat4 mat4::rotatexyz(const float a, const float b, const float c)
{
    mat4 M;
    const float ca = cosf(a), sa = sinf(a);
    const float cb = cosf(b), sb = sinf(b);
    const float cc = cosf(c), sc = sinf(c);

    M.cell[0] = cb * cc, M.cell[1] = -1 * cb * sc, M.cell[2] = sb;
    M.cell[4] = sa * sb * cc + ca * sc, M.cell[5] = -1 * sa * sb * sc + ca * cc, M.cell[6] = -1 * sa * cb;
    M.cell[8] = -1 * ca * sb * cc + sa * sc, M.cell[9] = ca * sb * sc + sa * cc, M.cell[10] = ca * cb;

    return M;
}

(This function basically creates this transformation matrix from the three camera orientation angles.)

I've tried to keep this question as compact as possible but I can provide more code if needed. I'm completely baffled about why I'm getting this kind of deformation on my spheres, and some insight would be invaluable! Thanks.

\$\endgroup\$
  • \$\begingroup\$ Transpose your matrices. Most APIs use column major ordering \$\endgroup\$ – Bálint Dec 2 '17 at 19:03
  • \$\begingroup\$ @Bálint I'm not using an API. Coding everything from scratch. I will try the transposition though. \$\endgroup\$ – Step Dec 2 '17 at 23:30
1
\$\begingroup\$

Well, first off, unlike transformation and scale, you can't just combine orientation matrices in order to get a real orientation matrix out. You have to apply the orientation matrices with respect to a single axis, apply them separately, or use a single complex valued quaternion matrix. You may be getting into something called a gimbal lock or some other strange positional artifact in combination with other bugs in your code.

Since your Y rotation doesn't change after going the other direction if you look at your gif, I suspect there is a bug in the code you aren't showing us, probably not updating rotation when going the opposite direction.

The easiest way to avoid gimbal locking is to just rotate with respect to each axis individually, something like this:

// because I don't want to use silly linear indexing
class mat3 : ...{
    ...
    // incase of inheritance
    int width(){
        return 3;
    }
    int height(){
        return 3;
    }
    // can put this in parent class if inherits width and height functions
    float& operator () (int row, int col){
        // if m_array is row ordered
        int linear_index = row * width() + col;
        // if m_array is column ordered
        int linear_index = col * height() + row;

        assert(linear_index < (width()*height()));

        return m_array[linear_index];
    }

    // duplicated for const usage
    const float& operator () (int row, int col) const{
        // if m_array is row ordered
        int linear_index = row * width() + col;
        // if m_array is column ordered
        int linear_index = col * height() + row;

        assert(linear_index < (width()*height()));

        return m_array[linear_index];
    }
    ...    
};

...

mat3 mat3::xAxisRotMat(const float radians){
    //from https://stackoverflow.com/questions/14607640/rotating-a-vector-in-3d-space
    mat3 m;

    float cos_rad = cosf(radians);
    float sin_rad = sinf(radians);

    m(0,0) = 1;
    m(0,1) = 0;
    m(0,2) = 0;

    m(1,0) = 0;
    m(1,1) = cos_rad;
    m(1,2) = -sin_rad;

    m(2,0) = 0;
    m(2,1) = sin_rad;
    m(2,2) = cos_rad;
}

mat3 mat3::yAxisRotMat(const float radians){
    //from https://stackoverflow.com/questions/14607640/rotating-a-vector-in-3d-space
    mat3 m;

    float cos_rad = cosf(radians);
    float sin_rad = sinf(radians);

    m(0,0) = cos_rad;
    m(0,1) = 0;
    m(0,2) = sin_rad;

    m(1,0) = 0;
    m(1,1) = 1;
    m(1,2) = 0;

    m(2,0) = -sin_rad;
    m(2,1) = 0;
    m(2,2) = cos_rad;
}

mat3 mat3::zAxisRotMat(const float radians){
    //from https://stackoverflow.com/questions/14607640/rotating-a-vector-in-3d-space
    mat3 m;

    float cos_rad = cosf(radians);
    float sin_rad = sinf(radians);

    m(0,0) = cos_rad;
    m(0,1) = -sin_rad;
    m(0,2) = 0;

    m(1,0) = sin_rad;
    m(1,1) = cos_rad;
    m(1,2) = 0;

    m(2,0) = 0;
    m(2,1) = 0;
    m(2,2) = 1;
}

Now where you would normally use your camera to transform the points (and I guess rotate?) use these matrices. Assuming your using the opposite transformation method to move the world around you, first create your translation matrix and your rotation matrix, and to rotate each of your points, first translating, and then rotating with each of the respective axis transformations, you should be using a total of 4 matrix multiplications per point.

I should also point out that translating the vector [0,0,0] by another vector [x,y,z] just returns [x,y,z], because all a translation will do is add x, y, and z to the original xyz elements.

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.