Fast and easy projection matrix for 2d only rendering?

Like in topic. Is there any way to make fast and easy to use projection matrix only for 2d? Currently this is my code for projection matrix:

public Matrix4f perspectiveMatrix(int width, int height, float FOV, float near, float far) {
        Matrix4f matrix = new Matrix4f();
        
        float fieldOfView = FOV;
        float aspectRatio = (float)width / (float)height;
        float near_plane = near;
        float far_plane = far;

        float y_scale = this.coTangent(this.degreesToRadians(fieldOfView / 2f));
        float x_scale = y_scale / aspectRatio;
        float frustum_length = far_plane - near_plane;

        matrix.m00 = x_scale;
        matrix.m11 = y_scale;
        matrix.m22 = -((far_plane + near_plane) / frustum_length);
        matrix.m23 = -1;
        matrix.m32 = -((2 * near_plane * far_plane) / frustum_length);
        
        return matrix;
    }

The best matrix should have/be:
-easy coordinates system - above code in resolution 800X600 and FOV 60 is rendering values in ranges which are really hard to manage in 2d game. Best possible coordinate system should be width/height in x and 1 in y.
-fast - I need to render 5000+ quads with textures, scale and rotation - currently this amount of quads rendered on screen (random positions, scaling and rotations) is increasing my GPU load to about 95-100%

Also, are there more optimalizations for 2d possible?

Well, as the name of the perspective Matrix already says, it’s perspective. That means, the objects close to the viewer seem to be bigger than the objects more far away. Even more: It means the room is projected onto the viewer’s viewing plane (in this case the Framebuffer / PC screen) in the form of a trapeze / Cone.

The matrix you have there is a matrix used in 3D games.

What you want is an orthogonal matrix. Look at the libgdx code directly from “Matrix4” for example:


/** Sets the matrix to an orthographic projection like glOrtho (http://www.opengl.org/sdk/docs/man/xhtml/glOrtho.xml) following
 * the OpenGL equivalent
 * 
 * @param left The left clipping plane
 * @param right The right clipping plane
 * @param bottom The bottom clipping plane
 * @param top The top clipping plane
 * @param near The near clipping plane
 * @param far The far clipping plane
 * @return This matrix for the purpose of chaining methods together. */
public Matrix4 setToOrtho (float left, float right, float bottom, float top, float near, float far) {
	this.idt();
	float x_orth = 2 / (right - left);
	float y_orth = 2 / (top - bottom);
	float z_orth = -2 / (far - near);

	float tx = -(right + left) / (right - left);
	float ty = -(top + bottom) / (top - bottom);
	float tz = -(far + near) / (far - near);

	val[M00] = x_orth;
	val[M10] = 0;
	val[M20] = 0;
	val[M30] = 0;
	val[M01] = 0;
	val[M11] = y_orth;
	val[M21] = 0;
	val[M31] = 0;
	val[M02] = 0;
	val[M12] = 0;
	val[M22] = z_orth;
	val[M32] = 0;
	val[M03] = tx;
	val[M13] = ty;
	val[M23] = tz;
	val[M33] = 1;

	return this;
}

I have tried to implement this matrix, but it doesn’t work for me. Am I doing anything wrong?

I have changed this function into:

public Matrix4f orthoMatrix(float left, float right, float bottom, float top, float near, float far) {
        Matrix4f matrix = new Matrix4f();
        float x_orth = 2 / (right - left);
        float y_orth = 2 / (top - bottom);
        float z_orth = -2 / (far - near);

        float tx = -(right + left) / (right - left);
        float ty = -(top + bottom) / (top - bottom);
        float tz = -(far + near) / (far - near);

        matrix.m00 = x_orth;
        matrix.m10 = 0;
        matrix.m20 = 0;
        matrix.m30 = 0;
        matrix.m01 = 0;
        matrix.m11 = y_orth;
        matrix.m21 = 0;
        matrix.m31 = 0;
        matrix.m02 = 0;
        matrix.m12 = 0;
        matrix.m22 = z_orth;
        matrix.m32 = 0;
        matrix.m03 = tx;
        matrix.m13 = ty;
        matrix.m23 = tz;
        matrix.m33 = 1;

        return matrix;
    }

Matrix initialization (screen resolution - 800X600):

projectionMatrix = gCore.matrix.orthoMatrix(-400, 400, -300, 300, 0.1f, 1.1f);

Rendering (I am using scaleCoords and scaleScale functions to be able to input more programmer-friendly values, currently from range 0;100):

public void startDrawing() {
        GL20.glUseProgram(activeProgram);
        
        GL30.glBindVertexArray(vaoId);
        GL20.glEnableVertexAttribArray(0);
        GL20.glEnableVertexAttribArray(1);
        GL20.glEnableVertexAttribArray(2);

        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
    }
    
    public void render(int texture, int shader, float x, float y, float scalex, float scaley, float angle) {
        x = scaleCoords(x);
        y = scaleCoords(y);
        scalex = scaleScale(scalex);
        scaley = scaleScale(scaley);
        if (shader!=activeProgram) {
            GL20.glUseProgram(shader);
            projectionMatrixLocation = GL20.glGetUniformLocation(shader, "projectionMatrix");
            viewMatrixLocation = GL20.glGetUniformLocation(shader, "viewMatrix");
            modelMatrixLocation = GL20.glGetUniformLocation(shader, "modelMatrix");
            activeProgram = shader;
        }

        modelAngle.z = angle;
        modelPos.x = x;
        modelPos.y = y;
        
        modelScale.x = scalex;
        modelScale.y = scaley;
        
        viewMatrix = new Matrix4f();
        modelMatrix = new Matrix4f();

        Matrix4f.translate(cameraPos, viewMatrix, viewMatrix);
        
        Matrix4f.translate(modelPos, modelMatrix, modelMatrix);
        Matrix4f.scale(modelScale, modelMatrix, modelMatrix);
        Matrix4f.rotate(this.degreesToRadians(modelAngle.z), new Vector3f(0, 0, 1), modelMatrix, modelMatrix);
        //Matrix4f.rotate(this.degreesToRadians(modelAngle.y), new Vector3f(0, 1, 0), modelMatrix, modelMatrix);
        //Matrix4f.rotate(this.degreesToRadians(modelAngle.x), new Vector3f(1, 0, 0), modelMatrix, modelMatrix);
        
        projectionMatrix.store(matrix44Buffer); matrix44Buffer.flip();
        GL20.glUniformMatrix4(projectionMatrixLocation, false, matrix44Buffer);
        viewMatrix.store(matrix44Buffer); matrix44Buffer.flip();
        GL20.glUniformMatrix4(viewMatrixLocation, false, matrix44Buffer);
        modelMatrix.store(matrix44Buffer); matrix44Buffer.flip();
        GL20.glUniformMatrix4(modelMatrixLocation, false, matrix44Buffer);
        
        if (texture!=activeTexture) {
            GL13.glActiveTexture(GL13.GL_TEXTURE0);
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
            activeTexture = texture;
        }

        GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
    }
    
    public void endRendering() {
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
        GL20.glDisableVertexAttribArray(0);
        GL20.glDisableVertexAttribArray(1);
        GL20.glDisableVertexAttribArray(2);
        GL30.glBindVertexArray(0);

        GL20.glUseProgram(0);
    }

    private float scaleCoords(float scale) {
        if (scale!=0) {
            scale=scale/50;
        }
        scale--;
        return scale;
    }
    
    private float scaleScale(float scale) {
        if (scale!=0) {
            scale=scale/50;
        }
        return scale;
    }

What doesn’t work for you? What’s wrong? Isn’t it rendering? Does it look skewed?

I see only black (LWJGL default) screen and nothing more. Any render values which are working perfectly in perspective matrix does not work in ortho matrix.

Here is what lwjgl-basics uses for 2D ortho matrix:

Basically modified from LibGDX.

Isn’t this almost the same code?

I am trying to find any solutions how to make this ortho work (I don’t know where is the problem - in my rendering or in matrix itself), but there is lack of (good) ortho matrix tutorials in the Internet. It seems that the only hope is in you. :slight_smile:

Yup, looks about the same.

Can’t really be of any help, since there are many things that could cause this problem. It’s hard to read and debug GL code when you mash it all into a single class.

It seems that the best solution is… Removing the whole projection matrix and leave only view and model matrixes, then scalling x in this way: wanted_scale/(game_width/game_height). Can I do the same using projection matrix?

FWIW this is what I do to enter/exit ‘2D mode’. 0,0 is the top-left corner and coordinates are pixel counts.

UIComponent.is2D is a global variable initially set to false.

Scala:


  /**
   * Call before drawing 2D elements on the glCanvas.
   */
  protected def toggleOn2D() {
    if (!UIComponent.is2D) {
      val viewport: IntBuffer = BufferUtils.createIntBuffer(16)

      glGetInteger(GL_VIEWPORT, viewport)

      glMatrixMode(GL_PROJECTION)
      glPushMatrix()
      glLoadIdentity()

      glOrtho(viewport.get(0), viewport.get(0)+viewport.get(2), viewport.get(1), viewport.get(1)+viewport.get(3), -1, 1)
      glMatrixMode(GL_MODELVIEW)
      glPushMatrix()
      glLoadIdentity()
      glTranslated(0.375, 0.375, 0.0)  // slight transition for exact pixel positioning

      glPushAttrib(GL_DEPTH_BUFFER_BIT)
      glDisable(GL_DEPTH_TEST)

      UIComponent.is2D = true
    }
  }

  /**
   * Call before drawing 3D elements on the glCanvas or doing 3D calculations
   * involving the projection matrix.
   */
  protected def toggleOff2D() {
    if (UIComponent.is2D) {
      glPopAttrib()
      glMatrixMode(GL_PROJECTION)
      glPopMatrix()
      glMatrixMode(GL_MODELVIEW)
      glPopMatrix()

      UIComponent.is2D = false
    }
  }

This is ‘old’ OpenGL way.

Is there any sense to add projection matrix if setting viewpoint to (height, height) and calculating aspect ratio (for things like interface rendering which must be always displayed in the fixed place on screen) works well?