# Thread: Camera with Look, Right and Up vectors

1. ## 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.

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;```
?

2. 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);
}

void
GLTK_Cam_MoveUp(GLTK_Cam* camera, float distance)
{
GLTK_Vec moveVec;

GLTK_Vec_Dup(moveVec, camera->vAxis);
GLTK_Vec_Scale(moveVec, distance);
}

void
GLTK_Cam_MoveRight(GLTK_Cam* camera, float distance)
{
GLTK_Vec moveVec;

GLTK_Vec_Dup(moveVec, camera->uAxis);
GLTK_Vec_Scale(moveVec, distance);
}```
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).

3. 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?

4. 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;

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?

5. Nevermind the position detail, i forgot i was saving the base position of the camera before transforming it into the camera matrix.

6. 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.

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. 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. 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.

```[    Ux    Vx     Nx   0  ]