Chapter 10: Transformation Matrices — The Math Behind 3D Transforms

Game Development Math for Unity3D June 2018 35 min read

What is a Matrix?

A matrix is a rectangular grid of numbers arranged in rows and columns. Matrices are used throughout mathematics and physics to represent linear transformations operations that map one set of coordinates to another. In 3D graphics, one particular type of matrix is ubiquitous: the 4×4 transformation matrix.

Every time Unity moves a GameObject, rotates it, or scales it, there is a 4×4 matrix operating behind the scenes. The Transform component is a high-level interface over that matrix. When the GPU renders a mesh, it multiplies every vertex position by a chain of matrices. Understanding matrices gives you direct access to that machinery.

The Identity Matrix

The simplest 4×4 matrix is the identity matrix. It is the equivalent of multiplying by 1: any point or vector you transform through it comes out unchanged.

// Identity matrix layout (rows × columns):
// | 1  0  0  0 |
// | 0  1  0  0 |
// | 0  0  1  0 |
// | 0  0  0  1 |

Matrix4x4 identity = Matrix4x4.identity;

A Transformation Matrix Encodes Three Things

A general 4×4 transformation matrix encodes rotation, scale, and translation in a single mathematical object. Unity's key constructor method is Matrix4x4.TRS TRS stands for Translation, Rotation, Scale.

The power of this encoding is that combining two transformations is as simple as multiplying two matrices. Multiply the parent matrix by the child matrix and you get the combined transform exactly what Unity does internally when computing world-space positions from a hierarchy of GameObjects.

Anatomy of a 4×4 Transform Matrix

// Column-major layout (Unity stores matrices column by column):
//
//  Column:   0        1        2        3
//          | Rx*sx   Ry*sy   Rz*sz   tx |   row 0
//          | Ux*sx   Uy*sy   Uz*sz   ty |   row 1
//          | Fx*sx   Fy*sy   Fz*sz   tz |   row 2
//          |  0       0       0       1 |   row 3
//
// Where R = Right axis, U = Up axis, F = Forward axis
// sx, sy, sz = scale on each axis
// tx, ty, tz = translation (position)
  • Columns 0, 1, 2 rows 0 to 2: the upper-left 3×3 submatrix. Each column is one of the three local axes (Right, Up, Forward) scaled by the corresponding scale value. This submatrix encodes both rotation and scale combined.
  • Column 3 rows 0 to 2: the translation (world-space position of the object's origin).
  • Row 3: always (0, 0, 0, 1) for affine transforms.

The reason for using 4×4 matrices instead of 3×3 is precisely to handle translation. By working in 4D homogeneous coordinates (appending a w component of 1 to every position and 0 to every direction), translation becomes a matrix multiplication, which means all three operations can be composed with a single multiply.

Unity's Matrix4x4 Class

Creating a TRS Matrix

Vector3    position = new Vector3(3f, 0f, 5f);
Quaternion rotation = Quaternion.Euler(0f, 45f, 0f);
Vector3    scale    = new Vector3(1f, 1f, 1f);

Matrix4x4 m = Matrix4x4.TRS(position, rotation, scale);

Extracting Components from a Matrix

// Position (column 3, rows 0-2)
Vector3 extractedPosition = m.GetColumn(3);

// Local axes
Vector3 rightAxis   = m.GetColumn(0);   // transform.right
Vector3 upAxis      = m.GetColumn(1);   // transform.up
Vector3 forwardAxis = m.GetColumn(2);   // transform.forward

// Rotation (removes scale from the rotation-scale columns)
Quaternion extractedRotation = m.rotation;

// Lossy scale (magnitude of each column of the rotation-scale submatrix)
Vector3 extractedScale = m.lossyScale;

Transforming Points and Directions

Vector3 localPoint     = new Vector3(1f, 0f, 0f);
Vector3 worldPoint     = m.MultiplyPoint3x4(localPoint);   // includes translation
Vector3 worldDirection = m.MultiplyVector(localPoint);     // ignores translation
  • MultiplyPoint3x4 use for positions. It applies rotation, scale, and translation. A local-space point comes out in world space.
  • MultiplyVector use for directions and normals. It applies rotation and scale but not translation.

Inverting a Matrix

Matrix4x4 inverse = Matrix4x4.Inverse(m);

// Transform from world space back to local space:
Vector3 localPoint = inverse.MultiplyPoint3x4(worldPoint);

When to Use Matrices

Custom Shaders and HLSL

In shader code (HLSL / Cg), there is no Transform component. Vertices are transformed by matrix multiplication explicitly. The standard Unity shader matrices are:

  • unity_ObjectToWorld the Model matrix. Transforms a vertex from object space to world space.
  • UNITY_MATRIX_V the View matrix. Transforms from world space to camera space.
  • UNITY_MATRIX_P the Projection matrix. Transforms from camera space to clip space.
  • UnityObjectToClipPos(vertex) convenience function that applies Model × View × Projection in one call.

Batch Point Transformations

If you need to transform thousands of points from local space to world space every frame, building a single Matrix4x4 and calling MultiplyPoint3x4 in the loop is faster because the matrix is constructed only once.

Matrix4x4 localToWorld = transform.localToWorldMatrix;

for (int i = 0; i < localPoints.Length; i++)
{
    worldPoints[i] = localToWorld.MultiplyPoint3x4(localPoints[i]);
}

Saving and Restoring Transforms

// Save
Matrix4x4 snapshot = transform.localToWorldMatrix;

// Restore
transform.position   = snapshot.GetColumn(3);
transform.rotation   = snapshot.rotation;
transform.localScale = snapshot.lossyScale;

Graphics.DrawMeshInstanced GPU Instancing

GPU instancing lets you render thousands of identical meshes (trees, rocks, enemies) in a single draw call by passing an array of Matrix4x4 transforms to the GPU.

Custom TRS Transform Example

using UnityEngine;

public class MatrixDemo : MonoBehaviour
{
    public Transform referenceObject;
    public Vector3 localPoint = new Vector3(1f, 0f, 0f);

    void Update()
    {
        // High-level approach
        Vector3 worldPointA = referenceObject.TransformPoint(localPoint);

        // Matrix approach equivalent result
        Matrix4x4 m = Matrix4x4.TRS(
            referenceObject.position,
            referenceObject.rotation,
            referenceObject.lossyScale
        );
        Vector3 worldPointB = m.MultiplyPoint3x4(localPoint);

        // Both should be identical
        Debug.DrawLine(worldPointA, worldPointB, Color.red);
    }
}

Combining Matrices

When you parent a GameObject in Unity, the engine combines the parent's transform and the child's local transform to compute the child's world transform. The underlying operation is matrix multiplication:

Matrix4x4 parentMatrix = parentTransform.localToWorldMatrix;
Matrix4x4 childLocal   = Matrix4x4.TRS(
    childLocalPosition,
    childLocalRotation,
    childLocalScale
);

// Combined world transform of the child
Matrix4x4 childWorld = parentMatrix * childLocal;

Order Matters

Matrix multiplication is not commutative. A * B is generally not the same as B * A. The convention is: apply transforms from right to left. In the expression parentMatrix * childLocal, the child's local transform is applied first, then the parent's world transform is applied to the result.

Matrices in Shaders

// Minimal Unity vertex shader using matrices explicitly:
Shader "Custom/MatrixDemo"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata { float4 vertex : POSITION; };
            struct v2f    { float4 pos : SV_POSITION; float3 worldPos : TEXCOORD0; };

            v2f vert(appdata v)
            {
                v2f o;
                // Standard clip-space transform (Model-View-Projection)
                o.pos = UnityObjectToClipPos(v.vertex);

                // World-space position using the Object-To-World matrix
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(i.worldPos * 0.1, 1.0);
            }
            ENDCG
        }
    }
}

Practical Example: GPU Instancing Setup

using UnityEngine;

public class GPUInstancedRenderer : MonoBehaviour
{
    public Mesh mesh;
    public Material material;       // must have GPU instancing enabled
    public int instanceCount = 500;
    public float spread = 20f;

    private Matrix4x4[] matrices;

    void Start()
    {
        matrices = new Matrix4x4[instanceCount];
        for (int i = 0; i < instanceCount; i++)
        {
            Vector3    position = new Vector3(
                Random.Range(-spread, spread),
                0f,
                Random.Range(-spread, spread)
            );
            Quaternion rotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
            Vector3    scale    = Vector3.one * Random.Range(0.5f, 1.5f);

            matrices[i] = Matrix4x4.TRS(position, rotation, scale);
        }
    }

    void Update()
    {
        // One draw call renders all instances
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices);
    }
}

Enable Enable GPU Instancing on the material in the Inspector. With this single script and a simple material, you can render 500 trees or rocks in one draw call.

Animating Instances

void Update()
{
    float t = Time.time;
    for (int i = 0; i < matrices.Length; i++)
    {
        Vector3 basePos  = matrices[i].GetColumn(3);
        Vector3 animPos  = basePos + Vector3.up * Mathf.Sin(t + i * 0.5f) * 0.3f;

        matrices[i] = Matrix4x4.TRS(animPos,
                                    matrices[i].rotation,
                                    matrices[i].lossyScale);
    }
    Graphics.DrawMeshInstanced(mesh, 0, material, matrices);
}

Matrix vs Transform When to Prefer Each

Use the Transform API (99% of the time)

  • Moving, rotating, and scaling GameObjects during gameplay
  • Parenting and unparenting objects
  • Converting points between local and world space on a per-frame basis
  • Any situation where you are working with a small number of objects
  • Anything involving physics (always drive physics through Rigidbody, not Transform)

Use Matrix4x4 Directly

  • Graphics API Graphics.DrawMeshInstanced and similar low-level rendering calls require matrix arrays.
  • Unity Jobs and Burst Job structs cannot access MonoBehaviour or Transform. Matrix math is fully blittable and runs safely in Jobs.
  • Shader data passing transform data to shaders through Material property blocks or constant buffers requires matrices.
  • Mathematical operations on many points computing the matrix once and reusing it is faster than calling TransformPoint repeatedly.
  • Serialized transform data storing a complete transform as a single Matrix4x4 value is compact and fast to read back.

Summary

Transformation matrices are the mathematical bedrock of all 3D graphics. While Unity's high-level APIs hide them for everyday use, understanding matrices gives you access to the full power of the rendering pipeline. Here is what you can now do:

  • Explain what a 4×4 transformation matrix is and why 4×4 (not 3×3) is used
  • Read the anatomy of a TRS matrix: rotation-scale in the upper-left 3×3, translation in column 3
  • Create a Matrix4x4 with Matrix4x4.TRS
  • Extract position, rotation, scale, and local axes from a matrix
  • Transform points and directions through a matrix
  • Invert a matrix to reverse a transform
  • Combine matrices with multiplication (and know that order matters)
  • Use GPU instancing with Graphics.DrawMeshInstanced
  • Understand where shader matrices come from and what they do

With this chapter, you have completed the mathematical toolkit of a professional Unity3D developer. The next and final section of the book is the conclusion a full recap of everything you have learned and a roadmap for where to go next.

Unity3D Matrices Matrix4x4 Transform Mathematics Shaders C#

Need help with Unity3D development?

I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.

Start a Conversation