Camera with Look, Right and Up vectors

This is a discussion on Camera with Look, Right and Up vectors within the Game Programming forums, part of the General Programming Boards category; Hi, I am trying to implement a camera using both quaternions and axis angles. I'm new to the maths in ...

  1. #1
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7

    Camera with Look, Right and Up vectors

    Hi,

    I am trying to implement a camera using both quaternions and axis angles.
    I'm new to the maths in this area (started 2 weeks ago), so, no experience

    Since i found this thread on this forum today, i was using glm::lookat function to move the camera around, but since it is said here that is not the best way, i want now to implement it the right way.

    Anyway, from the thread:
    How to properly move/strafe/yaw/pitch camera in OpenGL/GLUT using gluLookAt?

    i was about to try to implement the class from the example from post #10 but i don't know what the following functions do and it's maths involved:
    Code:
    m = MatrixRotationAxis(&m_up,units);
    The variable 'units' is the angle of rotation to apply to the matrix 'm' given the 'm_up' vector or is it a unit of displacement to apply on the 'm_up' vector and transform into the 'm' matrix?

    Code:
    TransformVectorCoord(&m_right,&m);
    Is it applying the 'm_right' to the matrix like:
    Code:
    m[0][1] = m_right.x;
    m[1][1] = m_right.y;
    m[2][1] = m_right.z;
    ?

    Thanks in advance for anything

  2. #2
    Registered User MacNilly's Avatar
    Join Date
    Oct 2005
    Posts
    326
    You can develop a "frame" (short for frame of reference I guess) class that consists of 4 vectors: n, u, and v, and position. The N, U, and V are basically your forward, right, and up vectors. Position is self-expanatory. Then if you have a matrix

    [ Ux Vx Nx Px ]
    [ Uy Vy Ny Py ]
    [ Uz Vz Nz Pz ]
    [ 0 0 0 1 ]

    and multiply this matrix by a vector, it will transform the vector into the 3d coordinate system (or "space") described by U,V,N,and P vectors: they form a "basis" for the space.

    For a camera though, you want something a bit different: i.e., if you rotate the camera left, what you really want is a matrix that will rotate everything to the right. This is called the "duality of the viewing and modeling transforms." In order to accomplish this, you use the same idea as above but you transpose the U,V,and N vectors and use the negative of P.

    that is, this is a camera transform matrix:

    [ Ux Uy Uz -U.P ]
    [ Vx Vy Vz -V.P ]
    [ Nx Ny Nz -Z.P ]
    [ 0 0 0 1 ]

    where -U.P is the dot product of U and P, etc... and of course U,V and N are normalized.

    Here's some example code for such a camera:

    Code:
    #include "cam.h"
    #include <math.h>
    
    void
    GLTK_Cam_GetTransform(GLTK_Cam* camera, GLTK_Mtx m)
    {
        m[0] = camera->uAxis[0];
        m[1] = camera->vAxis[0];
        m[2] = -camera->nAxis[0];
        m[3] = 0.0;
        m[4] = camera->uAxis[1];
        m[5] = camera->vAxis[1];
        m[6] = -camera->nAxis[1];
        m[7] = 0.0;
        m[8] = camera->uAxis[2];
        m[9] = camera->vAxis[2];
        m[10] = -camera->nAxis[2];
        m[11] = 0.0;
        m[12] = -GLTK_Vec_Dot(camera->uAxis, camera->pos);
        m[13] = -GLTK_Vec_Dot(camera->vAxis, camera->pos);
        m[14] = GLTK_Vec_Dot(camera->nAxis, camera->pos);
        m[15] = 1.0;
    }
    
    void
    GLTK_Cam_Init(GLTK_Cam* camera)
    {
        GLTK_Vec_Set(camera->uAxis, 1.0, 0.0, 0.0, 1.0);
        GLTK_Vec_Set(camera->vAxis, 0.0, 1.0, 0.0, 1.0);
        GLTK_Vec_Set(camera->nAxis, 0.0, 0.0, -1.0, 1.0);
        GLTK_Vec_Set(camera->pos, 0.0, 0.0, 0.0, 1.0);
    }
    
    void
    GLTK_Cam_LookAt(GLTK_Cam* camera,
                    GLTK_Vec  location,
                    GLTK_Vec  target,
                    GLTK_Vec  up)
    {
        GLTK_Vec_Dup(camera->pos, location);
        GLTK_Vec_Dup(camera->nAxis, target);
        GLTK_Vec_Subv(camera->nAxis, location);
        GLTK_Vec_Normalize(camera->nAxis);
        GLTK_Vec_Normalize(up);
        GLTK_Vec_Dup(camera->uAxis, camera->nAxis);
        GLTK_Vec_Cross(camera->uAxis, up);
        GLTK_Vec_Normalize(camera->uAxis);
        GLTK_Vec_Dup(camera->vAxis, camera->uAxis);
        GLTK_Vec_Cross(camera->vAxis, camera->nAxis);
    }
    
    #if 0
    void
    GLTK_Cam_LookAt(GLTK_Cam* camera,
                    GLTK_Vec  location,
                    GLTK_Vec  target,
                    GLTK_Vec  up)
    {
        GLTK_Vec_Dup(camera->pos, location);
        GLTK_Vec_Dup(camera->nAxis, location);
        GLTK_Vec_Sub(camera->nAxis, target);
        GLTK_Vec_Normalize(camera->nAxis);
        GLTK_Vec_Normalize(up);
        GLTK_Vec_Dup(camera->uAxis, camera->nAxis);
        GLTK_Vec_Cross(camera->uAxis, up);
        GLTK_Vec_Normalize(camera->uAxis);
        GLTK_Vec_Dup(camera->vAxis, camera->uAxis);
        GLTK_Vec_Cross(camera->vAxis, camera->nAxis);
    }
    #endif
    
    void
    GLTK_Cam_Yaw(GLTK_Cam* camera, float angle)
    {
        GLTK_Mtx r;
        
        GLTK_Mtx_SetToIdentity(r);
        GLTK_Vec_Normalize(camera->vAxis);
        GLTK_Mtx_Rotate(r, -angle, camera->vAxis);
        GLTK_Mtx_Mulv(r, camera->nAxis);
        GLTK_Vec_Normalize(camera->nAxis);
        GLTK_Vec_Dup(camera->uAxis, camera->nAxis);
        GLTK_Vec_Cross(camera->uAxis, camera->vAxis);
        GLTK_Vec_Normalize(camera->uAxis);
    }
    
    void
    GLTK_Cam_Pitch(GLTK_Cam* camera, float angle)
    {
        GLTK_Mtx r;
        
        GLTK_Mtx_SetToIdentity(r);
        GLTK_Vec_Normalize(camera->uAxis);
        GLTK_Mtx_Rotate(r, -angle, camera->uAxis);
        GLTK_Mtx_Mulv(r, camera->nAxis);
        GLTK_Vec_Normalize(camera->nAxis);
        GLTK_Vec_Dup(camera->vAxis, camera->uAxis);
        GLTK_Vec_Cross(camera->vAxis, camera->nAxis);
        GLTK_Vec_Normalize(camera->vAxis);
    }
    
    void
    GLTK_Cam_Roll(GLTK_Cam* camera, float angle)
    {
    	GLTK_Mtx r;
    	
    	GLTK_Mtx_SetToIdentity(r);
    	GLTK_Vec_Normalize(camera->nAxis);
    	GLTK_Mtx_Rotate(r, -angle, camera->nAxis);
    	GLTK_Mtx_Mulv(r, camera->vAxis);
    	GLTK_Vec_Normalize(camera->vAxis);
    	GLTK_Vec_Dup(camera->uAxis, camera->nAxis);
    	GLTK_Vec_Cross(camera->uAxis, camera->vAxis);
    	GLTK_Vec_Normalize(camera->uAxis);
    }
    
    void
    GLTK_Cam_MoveForward(GLTK_Cam* camera, float distance)
    {
        GLTK_Vec moveVec;
        
        GLTK_Vec_Dup(moveVec, camera->nAxis);
        GLTK_Vec_Scale(moveVec, distance);
        GLTK_Vec_Addv(camera->pos, moveVec);
    }
    
    void
    GLTK_Cam_MoveUp(GLTK_Cam* camera, float distance)
    {
        GLTK_Vec moveVec;
        
        GLTK_Vec_Dup(moveVec, camera->vAxis);
        GLTK_Vec_Scale(moveVec, distance);
        GLTK_Vec_Addv(camera->pos, moveVec);
    }
    
    void
    GLTK_Cam_MoveRight(GLTK_Cam* camera, float distance)
    {
        GLTK_Vec moveVec;
        
        GLTK_Vec_Dup(moveVec, camera->uAxis);
        GLTK_Vec_Scale(moveVec, distance);
        GLTK_Vec_Addv(camera->pos, moveVec);
    }
    By the way, I would ignore quaternions and euler angles; they are both more difficult (IMO) to understand than the Linear Algebra approach (this one).
    Last edited by MacNilly; 11-15-2013 at 11:38 AM.

  3. #3
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    From what i read, Euler angles are easy to understand but are heavy on maths, where quaternions are lighter on maths (although more complicated) and don't suffer from gimbal locks.

    Not sure whether quaternions are lighter on maths then this approach, but does it suffer from gimbal lock?

    Thank you for your time.

  4. #4
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    I created a simple camera from your example and it seems to work very well. I do have a few problems though.

    The camera code i implemented from your example:
    Code:
    typedef struct cam_s {
        GLfloat speed;
    
        glm::mat4 viewMatrix;
        glm::vec3 front;
        glm::vec3 right;
        glm::vec3 up;
        glm::vec3 position;
    
        void InitializeCam(void) {
            speed = 0.05f;
    
            viewMatrix = glm::mat4(1.0f);
            right      = glm::vec3(1,0,0);
            up         = glm::vec3(0,1,0);
            front      = glm::vec3(0,0,1);
            position   = glm::vec3(0,0,0);
        }
        glm::mat4 GetTransform(void) {
            glm::mat4 m;
    
            m[0] = glm::vec4(right, -glm::dot(right, position));
            m[1] = glm::vec4(up,    -glm::dot(up, position));
            m[2] = glm::vec4(front, -glm::dot(front, position));
            m[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    
            return glm::transpose(m);
        }
    
        void Move(float speed) {
            position += speed * front;
        }
        void Yaw(float angle) {
            glm::mat4 rmat(1.0f);
            glm::vec4 tmp;
    
            up = glm::normalize(up);
    
            rmat = glm::rotate(rmat, -angle, up);
            tmp = rmat * glm::vec4(front, 0);
            front = glm::vec3(tmp.x, tmp.y, tmp.z);
            front = glm::normalize(front);
    
            right = glm::cross(front, up);
            right = glm::normalize(right);
        }
        void Pitch(float angle) {
            glm::mat4 rmat(1.0f);
            glm::vec4 tmp;
    
            right = glm::normalize(right);
    
            rmat = glm::rotate(rmat, -angle, right);
            tmp = rmat * glm::vec4(front, 0);
            front = glm::vec3(tmp.x, tmp.y, tmp.z);
            front = glm::normalize(front);
    
            up = glm::cross(right, front);
            up = glm::normalize(up);
        }
        void Roll(float angle) {
            glm::mat4 rmat(1.0f);
            glm::vec4 tmp;
    
            front = glm::normalize(front);
    
            rmat = glm::rotate(rmat, -angle, front);
            tmp = rmat * glm::vec4(up, 0);
            up = glm::vec3(tmp.x, tmp.y, tmp.z);
            up = glm::normalize(up);
    
            right = glm::cross(front, up);
            right = glm::normalize(right);
        }
    
        void Keyboard(GLFWwindow *window) {
            GLfloat angle = 0.15f;
    
            if(glfwGetKey(window, GLFW_KEY_KP_ADD) == GLFW_PRESS) {
                Move(-speed);
            }
            if(glfwGetKey(window, GLFW_KEY_KP_SUBTRACT) == GLFW_PRESS) {
                Move(speed);
            }
    
            if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
                Pitch(angle);
            }
            if(glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
                Pitch(-angle);
            }
            if(glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) {
                Yaw(angle);
            }
            if(glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) {
                Yaw(-angle);
            }
            if(glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
                Roll(-angle);
            }
            if(glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
                Roll(angle);
            }
        }
    } cam_t;
    When the program starts, everything is where it should be and in the right orientation. When i rotate the camera with either yaw, pitch or roll, the view will flip verticaly and OpenGL will invert culling, rendering hidden faces and hiding the front faces.

    I managed to fix this by negating the right, up, front vectors; This also demanded to negate the starting values of this vectors to reorientate everything in the right directions:
    Code:
        void InitializeCam(void) {
            speed = 0.05f;
    
            viewMatrix = glm::mat4(1.0f);
            right      = glm::vec3(-1,0,0);
            up         = glm::vec3(0,-1,0);
            front      = glm::vec3(0,0,-1);
            position   = glm::vec3(0,0,0);
        }
        glm::mat4 GetTransform(void) {
            glm::mat4 m;
    
            m[0] = glm::vec4(-right, -glm::dot(right, position));
            m[1] = glm::vec4(-up,    -glm::dot(up, position));
            m[2] = glm::vec4(-front, -glm::dot(front, position));
            m[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    
            return glm::transpose(m);
        }
    I don't believe this is the right way to fix this problem and i have no idea where the maths are wrong. It sure looks to be a common problem in all rotation functions.

    And, the position vector doesn't seem to update accordingly, even though the camera moves correctly in the space. X and Y coordinates only change when the camera rotates but not when i move sideways or up/downward. Only Z changes every time i move the camera around.

    Any help is welcome

    EDIT: The position vector changes according to the orientation of the camera relative to world space origin. Do i need to invert the camera matrix to obtain the world space coordinates of the camera?
    Last edited by mav; 11-15-2013 at 06:01 PM.

  5. #5
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    Nevermind the position detail, i forgot i was saving the base position of the camera before transforming it into the camera matrix.

  6. #6
    Registered User MacNilly's Avatar
    Join Date
    Oct 2005
    Posts
    326
    Comparing the GetTransform() function I posted with the one you posted, it doesn't appear you are correctly initializing the matrix elements. My matrix object is just a typedef for a C array with 16 elements, column-major order for use w/ OpenGL.

    I can confirm that the rotation and movement work correctly, it was a painstaking process to get it to work.

    You can manipulate the camera as much as you like, but you have to call GetTransform() explicitly to fill in the transformation matrix. Otherwise, it won't be updated: that is for efficiency. Then as I assume you know, you call glLoadMatrix() with the result.
    Last edited by MacNilly; 11-17-2013 at 09:25 PM.

  7. #7
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    Code:
    voidInitializeCam(void) {
       speed = 0.05f;
    
       viewMatrix = glm::mat4(1.0f);
       right      = glm::vec3(1,0,0);
       up         = glm::vec3(0,1,0);
       front      = glm::vec3(0,0,-1);
       position   = glm::vec3(0,0,0);
    }
    glm::mat4 GetTransform(void) {
       glm::mat4 m;
    
       m[0] = glm::vec4(right,  -glm::dot(right, position));
       m[1] = glm::vec4(up,     -glm::dot(up, position));
       m[2] = glm::vec4(-front, -glm::dot(front, position));
       m[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    
       return glm::transpose(m);
    }
    You were right about the initialization and i made the above changes but still didn't work. The camera Yaw and Pitch will rotate the camera around the world origin coordinates instead of the camera coordinates. Roll worked as expected though.

    I calculate the final transformation matrix in another part of the code like:
    Code:
    mvpMatrix = perspectiveMatrix * viewMatrix * modelMatrix;
    and pass it to the shaders with glUniform*():
    Code:
    glUniformMatrix4fv(mvpMatrixUniform, 1, GL_FALSE, glm::value_ptr(mvpMatrix));
    My thoughts were that i had gotten some minus sign wrong/missing in the yaw/pitch/roll functions... Not that i can tell anyway.

  8. #8
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    Got it fixed the right way i suppose.

    I switched the order of the cross product vectors and it did as i expected, that is, it gives the opposite perpendicular vector, which is what i wanted to get so that i had no need to negate the vectors at initialization and when calling GetTransform().

    Now InitializeCam() and GetTransform() can be done normaly:
    Code:
    void InitializeCam(void) {
        speed = 0.05f;
    
        right      = glm::vec3(1,0,0);
        up         = glm::vec3(0,1,0);
        front      = glm::vec3(0,0,1);
        position   = glm::vec3(0,0,0);
    }
    glm::mat4 GetTransform(void) {
        glm::mat4 m;
    
        m[0] = glm::vec4(right, -glm::dot(position, right));
        m[1] = glm::vec4(up,    -glm::dot(position, up));
        m[2] = glm::vec4(front, -glm::dot(position, front));
        m[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    
        return glm::transpose(m);
    }
    No more weird negations


    Thanks a lot for your help mate, really appreciated

  9. #9
    Registered User MacNilly's Avatar
    Join Date
    Oct 2005
    Posts
    326
    No problem.

    It bothers me though that it works under my setup but not yours. Although with this kind of thing, there's a million ways to go wrong. Checking out my 3D math book (more than 1 -- some are known to be wrong) I get this transform for the view matrix:

    Code:
    [ Ux Uy Uv 0 ] [ 1 0 0 -Tx ]     [ Ux Uy Uz -U.T ]
    [ Vx Vy Vz 0 ] [ 0 1 0 -Ty ]     [ Vx Vy Vz -V.T ]
    [ Nx Ny Nz 0 ] [ 0 0 1 -Tz ]  = [ Nx Ny Nz -N.T ]
    [ 0   0   0  1 ] [ 0 0 0  1   ]     [ 0   0   0   1    ]
    The first matrix aligns camera w/ world axis. The second translates to fixed origin. The product is the view transform matrix. In my code, however, I have negated the dot product of N in order to produce a right-handed coordinate system.

    In other words, I'm almost 100% sure this is the correct matrix.

    Edit: sorry about the bad formatting but it should be clear
    Last edited by MacNilly; 11-20-2013 at 09:10 AM.

  10. #10
    mav
    mav is offline
    Registered User mav's Avatar
    Join Date
    Nov 2013
    Posts
    7
    Yeah, if i'm correct, you might be using Direct3D as you graphics API which wants the view matrix to be row-major (if i'm correct).

    I am using OpenGL which expects the matrix to be column-major, hence the transpose in my code after filling the view matrix just like you do. I am using left-hand rule btw, perhaps i should have mentioned that earlier to avoid any confusion. Sorry about that

    That makes my final view matrix look like this (column-major):
    Code:
    [    Ux    Vx     Nx   0  ]
    [  Uy   Vy   Ny  0 ]
    [  Uz   Vz   Nz  0 ]
    [ -U.T  -V.T  -U.N   1  ]

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 4
    Last Post: 03-07-2012, 06:55 PM
  2. Vectors in vectors - pointers and iterators
    By fisherking in forum C++ Programming
    Replies: 8
    Last Post: 07-27-2010, 10:34 AM
  3. ip camera
    By sgh in forum C# Programming
    Replies: 3
    Last Post: 03-12-2009, 02:50 PM
  4. Following camera
    By pandu in forum Game Programming
    Replies: 5
    Last Post: 06-10-2008, 08:20 PM
  5. Digital Camera -> Slo-Mo Camera??
    By Masa in forum Tech Board
    Replies: 6
    Last Post: 12-24-2003, 11:11 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21