To define a perspective camera for CG related tasks, we need some basic information such as camera position (camPos), look at position (lookAt), field of view (fov), distance to near (dNear) and far (dFar) clipping planes, and finally ratio.

Camera position and lookAt are needed to define camera basis vectors, which are used to define camera matrix (C). We transform objects from world to camera space using inverse of camera matrix (invC) or from camera space to world space (using camera matrix). Objects (meshes) should be in camera space for the tasks such as backface culling, clipping and projection.

fov,  distance to near (dNear) and far (dFar) clipping planes, and ratio are used for perspective projection and frustum culling.

I used right-handed coordinate system for Enginator5000. So, I will explain everything in right-handed coordinate system.

Generate Camera Basis Vectors

Remember, in right handed coordinate system, if your origin is screen of the computer, +z is towards you, +y is towards ceiling, and +x is towards your right. To define camera basis vectors, we need to find three basis vectors. After we find these vectors, we will put them into a 4×4 matrix, and get camera matrix C.

Generally, people name camera basis vectors as u, v, w. I prefer to name them as camX, camY, and camZ. You can use whatever you want. Basis vectors are located in the C matrix as below:

 C =  Ux, Vx, Wx, camPosx
      Uy, Vy, Wy, camPosy
      Uz, Vz, Wz, camPosz
      0,  0,  0,     1

And you can calculate these vectors as below (C++ and GLM):

// Get +Z direction (W vector)
vec3 camZ = mCamPos - mLookAt;
camZ = glm::normalize(camZ);

// Get +X direction (U vector)
// U = cross(cameraUpVector, W) 
vec3 upV = glm::normalize(camUp); // For being sure
vec3 camX = glm::cross(upV, camZ);    

// Get +Y direction (V vector)
// V = cross(W, U)
vec3 camY = glm::cross(camZ, camX); // Ideally it is normalized

// Set camera matrix - GLM USES COLUMN MAJOR ORDER:
// C = [u, v, w, e; 0 0 0 1] -> C = [camX, camY, camZ, camPos; 0, 0, 0, 1]

C[0] = glm::vec4(camX, 0); // Set first column [Ux; Uy; Uz; 0]
C[1] = glm::vec4(camY, 0); // Set second column [Vx; Vy; Vz; 0]
C[2] = glm::vec4(camZ, 0); // Set third column [Wx; Wy; Wz; 0]
C[3] = glm::vec4(mCamPos, 1); // Set 4th column

Defining Perspective Projection Matrix

Perspective projection gives us realistic rendering results; however, it is not the only kinds of projections used in CG and video games. You can check this website to get an idea.

We define perspective projection matrix as below:

// Perspective projection matrix:
2n/(r-l),     0,         (r+l)/(r-l),        0
0,        2n /(t-b),     (t+b)/(t-b),        0
0,            0,        -(f+n)/(f-n),    -2fn/(f-n)
0,            0,            -1,              0

Where, n is distance to near plane (dNear), f is distance to far plane (dFar), r is right point on the near plane, l is left point on the near plane, t is top point on the near plane, and b is bottom on the near plane.

For more details, and derivation, you can check, ScratchaPixel, OGLdev or Schabby’s blog.

We calculate the values (r, l, t, b) as below (C++):

// tan() function takes radian. So, we should convert our fov
float tangent = (float)tan((fov * 0.5) * 3.14159265 / 180.0);

// Height and width values of near plane
float height = dNear * tangent;
float width = height * Ratio;

float top = height; // Calculate t
float bottom = -height; // Calculate b
float right = width; // Calculate r
float left = -width; // t: left = -top * ratio = bottom * ratio