Should I still use a 4x4 matrix for a 2D game with OpenGL?

The title says it all, really. In a 3D game, we use 4x4 matrices for all our transformations. Does this apply to 2D games too or should I use a different size matrix? And, I am using OpenGL. Thanks!

Even if it is 2D, you’d need the projection matrix, to project from the world space to the NDC. In case you need a moving camera, then you better go with a matrix, but you can optimize it to use a single vector offset.

Now considering the model matrix (better call it as sprite matrix I think) if all you need is positioning objects, there is no need to use the matrix in the first place, you can just go with a 2D vector.


uniform mat4 projView;  // AKA the combined camera matrix
uniform vec2 offset;      // The entity position

layout (location = 0) in vec2 position; // The vertex position

Then you simply multiply it with the projView matrix and get your output in NDC. In case you have rotation (2D rotations are just rotations on the z-axis) so you can get away with a 2D matrix for rotations:

You can simply multiply this matrix with your position to make it rotated. So the final shader might look like this:


#version 410

uniform mat4 projView;
uniform vec2 offset;
uniform float rotation;

layout (location = 0) in vec2 position;

void main()
{
    float sinR = sin(rotation);
    float cosR = cos(rotation);

    mat2 rot = mat2(cosR, sinR, -sinR, cosR);

    vec2 vPos = offset + (rot * position);
    gl_Position = viewProj * vec4(vPos, 0.0, 1.0);
}

I however, recommend against this, because it is not that necessary to super optimize the program. The second thing is, I believe that premature optimization is the root of all evil. If your hardware supports a lot of power, why not utilize it?

Technically, you only ever need a mat3x2 (3 columns x 2 rows) for everything in 2D, because you only need:

  • translation (third column)
  • scale (upper-left 2x2 submatrix)
  • rotation (also upper-left 2x2 submatrix)

Transforming from world-space (which is in 2D) to NDC (which is also in 2D***) only requires 2D scaling and 2D translation, because it is essentially an interval mapping from [left, right] -> [-1, +1] and [bottom, top] -> [-1, +1], so this:

Using the matrix in GLSL:


#version 120
uniform mat3x2 view;
in vec2 position;
void main() {
    gl_Position = vec4(view * vec3(position, 1.0), 0.0, 1.0);
}

***well, actually it is 3D, but we only care about the Z=0 plane

Latest 1.9.2-SNAPSHOT of JOML now has support for 3x2 matrices via Matrix3x2f specially geared for 2D, so you can get going right away. :slight_smile:

Here are some usage examples:


Matrix3x2f m = new Matrix3x2f()
// define a view of x in [-10, +10] and y in [-8, +8].
// Everything within these bounds is visible on the viewport:
.view(-10, +10, -8, +8)
// translate model to (2, 3):
.translate(2, 3)
// rotate model 45 degrees clockwise:
.rotate((float)Math.toRadians(45))
// and scale-down the model to half its size:
.scale(0.5f);

// Upload to OpenGL (via LWJGL):
FloatBuffer fb = <6 floats>;
GL21.glUniformMatrix3x2fv(mat3x2Loc, false, m.get(fb));
// or the old way:
FloatBuffer fb = <16 floats>;
GL11.glLoadMatrixf(m.get4x4(fb));

// Convert mouse coordinates (mouseX, mouseY) to world
// coordinates for a viewport of (800, 600):
float vpw = 800, vph = 600;
Vector2f world = new Vector2f();
m.unproject(mouseX, vph-mouseY, new int[] {0, 0, vpw, vph}, world);