Tutorial 33: Matrices and Transformations
Every 3D object you draw passes through three matrix transforms before it reaches the screen. Understanding these matrices — and how to compose them — is the foundation of all 3D rendering in CNA.
World / View / Projection Explained
Three coordinate spaces are involved in rendering a 3D scene:
- Object space — vertices are defined relative to the mesh origin.
- World space — the World matrix places the object in the shared scene.
- View space — the View matrix repositions everything relative to the camera.
- Clip space — the Projection matrix applies perspective and maps to [-1, 1].
The full transform applied to each vertex is:
clip_pos = Projection * View * World * vertex_pos;
In CNA (following XNA convention) you pass these three matrices separately to BasicEffect and the effect combines them internally.
Matrix::CreateTranslation
Matrix::CreateTranslation produces a matrix that moves an object to a position in world space.
// Move an object to (5, 0, 0) — e.g. position a planet
Matrix world = Matrix::CreateTranslation(5.0f, 0.0f, 0.0f);
// Or using a Vector3
Matrix world = Matrix::CreateTranslation(Vector3(5.0f, 0.0f, 0.0f));
Matrix::CreateRotationX / Y / Z
Rotates around the X, Y, or Z axis. Angles are always in radians.
float angle = MathHelper::ToRadians(45.0f); // 45 degrees → radians
Matrix rotX = Matrix::CreateRotationX(angle); // pitch
Matrix rotY = Matrix::CreateRotationY(angle); // yaw — most common "spin"
Matrix rotZ = Matrix::CreateRotationZ(angle); // roll
Radians everywhere. CNA follows XNA: all angle parameters use radians. Convert with MathHelper::ToRadians(degrees). Passing degrees directly gives a rotation 57× smaller than expected.
Matrix::CreateScale
Scales the mesh. Pass a single float for uniform scaling or a Vector3 for non-uniform.
Matrix bigSun = Matrix::CreateScale(2.0f); // 2× in all axes
Matrix tinyMoon = Matrix::CreateScale(0.3f); // 30% size
Matrix stretched = Matrix::CreateScale(Vector3(2, 1, 0.5f));
Matrix::CreateLookAt
CreateLookAt builds a view matrix from a camera position, a target point, and an up direction.
Matrix view = Matrix::CreateLookAt(
Vector3(0.0f, 10.0f, 20.0f), // camera position
Vector3::Zero, // look at origin
Vector3::Up // world up (0,1,0)
);
Matrix::CreatePerspectiveFieldOfView
Builds a perspective projection. The nearPlane should be as large as your scene allows — too small causes depth precision problems (see Tutorial 39).
float aspect = (float)screenWidth / (float)screenHeight;
Matrix proj = Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4, // 45° field of view (Pi/4 radians)
aspect, // e.g. 800/600 ≈ 1.333
0.1f, // near plane
1000.0f // far plane
);
Matrix Multiplication Order (TRS)
CNA uses the same row-major convention as XNA. When composing Scale, Rotation, and Translation, the order is:
// Correct TRS order: Scale first, Rotate second, Translate last
Matrix world = Matrix::CreateScale(scale)
* Matrix::CreateRotationY(rotation)
* Matrix::CreateTranslation(position);
Reversing the order gives wrong results — for example, translating before rotating causes the object to orbit the origin instead of spinning in place. Think of it as: first shrink/grow the mesh, then spin it, then move it to its final position.
Matrix::Invert
The inverse of a matrix undoes its transform. Common uses: converting world-space coordinates back to object space, or deriving a view matrix from a camera's world transform.
Matrix cameraWorld = Matrix::CreateTranslation(cameraPos)
* Matrix::CreateRotationY(yaw);
// View matrix = inverse of camera's world matrix
Matrix view = Matrix::Invert(cameraWorld);
// Some overloads return a bool (false if matrix is singular/non-invertible)
Matrix result;
bool ok = Matrix::Invert(someMatrix, result);
if (!ok) { /* degenerate matrix — scale is zero or similar */ }
Solar System Demo
This demo draws three objects — Sun, Earth, Moon — using parent-child transform composition. Each child's world matrix is built by multiplying its local transform by its parent's world matrix.
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Graphics/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/BasicEffect.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexBuffer.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexPositionColor.hpp"
#include "Microsoft/Xna/Framework/MathHelper.hpp"
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
// Build a simple flat cube (8 vertices, triangle list — 36 indices drawn as 12 triangles)
// Returns vertex count; caller passes verts array of size >=8.
static void BuildCube(std::vector<VertexPositionColor>& verts, Color col) {
float h = 0.5f;
Vector3 corners[8] = {
{-h,-h,-h},{+h,-h,-h},{+h,+h,-h},{-h,+h,-h},
{-h,-h,+h},{+h,-h,+h},{+h,+h,+h},{-h,+h,+h}
};
int faces[36] = {
0,1,2, 0,2,3, // back
4,6,5, 4,7,6, // front
0,3,7, 0,7,4, // left
1,5,6, 1,6,2, // right
3,2,6, 3,6,7, // top
0,4,5, 0,5,1 // bottom
};
verts.clear();
for (int i : faces)
verts.push_back({corners[i], col});
}
class SolarSystemGame final : public Game {
public:
SolarSystemGame() : graphics_(this) {
graphics_.setPreferredBackBufferWidth(800);
graphics_.setPreferredBackBufferHeight(600);
}
protected:
void LoadContent() override {
auto& gd = getGraphicsDeviceProperty();
effect_ = std::make_unique<BasicEffect>(gd);
effect_->setVertexColorEnabled(true);
// Sun — yellow
BuildCube(sunVerts_, Color::Yellow);
sunVB_ = std::make_unique<VertexBuffer>(gd,
VertexPositionColor::VertexDeclaration,
(int)sunVerts_.size(), BufferUsage::None);
sunVB_->SetData(sunVerts_.data(), (int)sunVerts_.size());
// Earth — blue-green
BuildCube(earthVerts_, Color(0, 100, 200));
earthVB_ = std::make_unique<VertexBuffer>(gd,
VertexPositionColor::VertexDeclaration,
(int)earthVerts_.size(), BufferUsage::None);
earthVB_->SetData(earthVerts_.data(), (int)earthVerts_.size());
// Moon — light grey
BuildCube(moonVerts_, Color(180, 180, 180));
moonVB_ = std::make_unique<VertexBuffer>(gd,
VertexPositionColor::VertexDeclaration,
(int)moonVerts_.size(), BufferUsage::None);
moonVB_->SetData(moonVerts_.data(), (int)moonVerts_.size());
}
void Update(GameTime& gameTime) override {
float dt = (float)gameTime.getElapsedGameTime().TotalSeconds();
sunAngle_ += dt * 0.3f; // slow spin
earthOrbit_ += dt * 1.0f; // orbit speed
earthSpin_ += dt * 2.5f; // self-rotation
moonOrbit_ += dt * 3.0f; // moon orbits earth
}
void Draw(const GameTime&) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color(5, 5, 20)); // near-black sky
Matrix view = Matrix::CreateLookAt(
Vector3(0.0f, 12.0f, 18.0f),
Vector3::Zero,
Vector3::Up);
Matrix proj = Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4, 800.0f / 600.0f, 0.1f, 1000.0f);
effect_->setView(view);
effect_->setProjection(proj);
// --- Sun (center, large, slow spin) ---
Matrix sunWorld = Matrix::CreateScale(2.0f)
* Matrix::CreateRotationY(sunAngle_);
DrawMesh(*sunVB_, sunWorld);
// --- Earth (orbits sun, spins on own axis) ---
Matrix earthLocal = Matrix::CreateScale(0.7f)
* Matrix::CreateRotationY(earthSpin_)
* Matrix::CreateTranslation(6.0f, 0.0f, 0.0f);
Matrix earthOrbitMat = Matrix::CreateRotationY(earthOrbit_);
Matrix earthWorld = earthLocal * earthOrbitMat; // child * parent
DrawMesh(*earthVB_, earthWorld);
// --- Moon (orbits earth, inherits earth's position) ---
Matrix moonLocal = Matrix::CreateScale(0.3f)
* Matrix::CreateTranslation(1.8f, 0.0f, 0.0f);
Matrix moonOrbitMat = Matrix::CreateRotationY(moonOrbit_);
// Moon is child of Earth: local orbit → orbit around earth centre → earth world
Matrix moonWorld = moonLocal * moonOrbitMat * earthOrbitMat
* Matrix::CreateTranslation(6.0f, 0.0f, 0.0f);
DrawMesh(*moonVB_, moonWorld);
gd.Present();
}
private:
void DrawMesh(VertexBuffer& vb, const Matrix& world) {
auto& gd = getGraphicsDeviceProperty();
effect_->setWorld(world);
gd.setVertexBuffer(vb);
for (auto& pass : effect_->getCurrentTechnique().Passes) {
pass.Apply();
gd.DrawPrimitives(PrimitiveType::TriangleList, 0,
vb.getVertexCount() / 3);
}
}
GraphicsDeviceManager graphics_;
std::unique_ptr<BasicEffect> effect_;
std::vector<VertexPositionColor> sunVerts_, earthVerts_, moonVerts_;
std::unique_ptr<VertexBuffer> sunVB_, earthVB_, moonVB_;
float sunAngle_ = 0.0f;
float earthOrbit_ = 0.0f;
float earthSpin_ = 0.0f;
float moonOrbit_ = 0.0f;
};
int main() { SolarSystemGame game; game.Run(); }
Next Steps
- Tutorial 34: 3D Camera Setup and Control — build a flyable FPS camera using these same matrices.
- Tutorial 32: BasicEffect and 3D Lighting — add lights to your transformed objects.
- Math Types reference — full Matrix API.