Camera & Transformations: Transformation Matrices
Transformation Matrices
In this section, we’ll dive deeper into the transformation matrices used in 3D graphics and how they’re applied in our rendering pipeline.
The Model-View-Projection (MVP) Pipeline
The transformation of vertices from object space to screen space involves a series of matrix multiplications, commonly known as the MVP pipeline:
// The complete transformation pipeline
glm::mat4 MVP = projectionMatrix * viewMatrix * modelMatrix;
Let’s explore each of these matrices in detail.
Model Matrix
The model matrix transforms vertices from object space to world space. It positions, rotates, and scales objects in the world.
glm::mat4 createModelMatrix(
const glm::vec3& position,
const glm::vec3& rotation,
const glm::vec3& scale
) {
// Start with identity matrix
glm::mat4 model = glm::mat4(1.0f);
// Apply transformations in order: scale, rotate, translate
model = glm::translate(model, position);
// Apply rotations around each axis
model = glm::rotate(model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
model = glm::rotate(model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::rotate(model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
// Apply scaling
model = glm::scale(model, scale);
return model;
}
View Matrix
The view matrix transforms vertices from world space to view space (camera space). It represents the position and orientation of the camera.
glm::mat4 createViewMatrix(
const glm::vec3& cameraPosition,
const glm::vec3& cameraTarget,
const glm::vec3& upVector
) {
return glm::lookAt(cameraPosition, cameraTarget, upVector);
}
The lookAt function creates a view matrix that positions the camera at cameraPosition, looking at cameraTarget, with upVector defining the up direction.
Projection Matrix
The projection matrix transforms vertices from view space to clip space. It defines how 3D coordinates are projected onto the 2D screen.
Perspective Projection
Perspective projection simulates how objects appear smaller as they get farther away, which is how our eyes naturally perceive the world.
glm::mat4 createPerspectiveMatrix(
float fovY,
float aspectRatio,
float nearPlane,
float farPlane
) {
return glm::perspective(glm::radians(fovY), aspectRatio, nearPlane, farPlane);
}
Parameters:
-
fovY: Field of view angle in degrees (vertical) -
aspectRatio: Width divided by height of the viewport -
nearPlane: Distance to the near clipping plane -
farPlane: Distance to the far clipping plane
Orthographic Projection
Orthographic projection doesn’t have perspective distortion, making it useful for 2D rendering or technical drawings.
glm::mat4 createOrthographicMatrix(
float left,
float right,
float bottom,
float top,
float nearPlane,
float farPlane
) {
return glm::ortho(left, right, bottom, top, nearPlane, farPlane);
}
Normal Matrix
When applying non-uniform scaling to objects, normals can become incorrect if transformed with the model matrix. The normal matrix solves this issue:
glm::mat3 createNormalMatrix(const glm::mat4& modelMatrix) {
// The normal matrix is the transpose of the inverse of the upper-left 3x3 part of the model matrix
return glm::transpose(glm::inverse(glm::mat3(modelMatrix)));
}
Applying Transformations in Shaders
In Vulkan, we typically pass these matrices to our shaders as uniform variables:
// Vertex shader
#version 450
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragNormal;
layout(location = 1) out vec2 fragTexCoord;
void main() {
// Apply MVP transformation
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
// Transform normal using normal matrix
mat3 normalMatrix = transpose(inverse(mat3(ubo.model)));
fragNormal = normalMatrix * inNormal;
fragTexCoord = inTexCoord;
}
Hierarchical Transformations
For complex objects or scenes with parent-child relationships, we use hierarchical transformations:
// Parent transformation
glm::mat4 parentModel = createModelMatrix(parentPosition, parentRotation, parentScale);
// Child transformation relative to parent
glm::mat4 localModel = createModelMatrix(childLocalPosition, childLocalRotation, childLocalScale);
// Combined transformation
glm::mat4 childWorldModel = parentModel * localModel;
In the next section, we’ll implement a camera system that uses these transformation concepts to navigate our 3D scenes.