3

I try to implement transforms in my 2D graphics program but I encounter a mystery (even if it's a noob topic, sorry). My example code tries to translates a quad to the center of the screen then rotate by 45 degrees with the center as a pivot but something goes wrong.

The order of the matrix operation was taken from learnopengl.com https://learnopengl.com/code_viewer.php?code=in-practice/breakout/sprite_renderer

import math
type 
    OGLfloat = float32
    OGLuint = uint32
    OGLint = int32

const 
    POSITION_LENGTH = 3.OGLint
    COLOR_LENGTH = 4.OGLint

const
    WINDOW_W = 640
    WINDOW_H = 480

let
    colorDataOffset = POSITION_LENGTH * OGLint(sizeof(OGLfloat))

#[ Then many Opengl constants and functions translation from C, not pasted here. 
Nim users knows it's easy to do.]#

var 
    vertices = @[OGLfloat(-1.0), 1.0, 0, # Position   
                                0, 0, 1, 1, # Color
                          0, 1.0, 0,
                                0, 0, 1, 1,
                          0, 0, 0,
                                0, 0, 1, 1,
                          -1.0, 0.0, 0,
                                0, 0, 1, 1
                          ]

    indices = @[OGLuint(0), 1, 2, 2, 3, 0]

type Mat4x4* = array[16, OGLfloat] # 4 x 4 Matrix

# The operation who will concatenate the translation, rotation and scaling matrices.
proc `*`(a, b:Mat4x4):Mat4x4 =
    result[0] = a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3]
    result[1] = a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3]   
    result[2] = a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3]
    result[3] = a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3]

    result[4] = a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7]
    result[5] = a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7]
    result[6] = a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7]
    result[7] = a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7] 

    result[8] = a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11]
    result[9] = a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11]
    result[10] = a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11]
    result[11] = a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11]

    result[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15]
    result[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15]
    result[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15]
    result[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]

proc rotation2D(deg:float):Mat4x4 =
    let
        rad = PI * deg / 180 # convert degrees to radians
        s = OGLfloat(sin(rad))
        c = OGLfloat(cos(rad))
    result = [c, s, 0, 0,
            - s, c, 0, 0,
              0, 0, 1, 0,
              0, 0, 0, 1]

proc translation(x,y:float):Mat4x4 = #{.noInit.} =
  result = [OGLfloat(1), 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    x, y, 0, 1]

proc scaling(x,y:float):Mat4x4 =
    result = [OGLfloat(x), 0, 0, 0, 
                        0, y, 0, 0, 
                        0, 0, 1, 0, 
                        0, 0, 0, 1]

var 
    #[ The order of the operations was taken from "learnopengl.com" 
    but without the help of GLM, thanks to the previous functions.]#

    modelMatrix = translation(1.0, -1.0) * # move to the screen center
                  translation(-0.5, 0.5) * # move to the quad center
                  rotation2D(45.0) * # rotation on Z axis
                  translation(0.5, -0.5) * # re-move to the quad center ???
                  scaling(1.0, 1.0) # change nothing with these values.

# Init the system and pop the window.
var glfwErr = glfwInit()
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
var winHandle = glfwCreateWindow(WINDOW_W, WINDOW_H)
glfwMakeContextCurrent(winHandle)
var glewErr = glewInit()

# Shaders
var
    shadID:OGLuint
    vertSrc:cstring = """
        #version 330 core
        layout (location = 0) in vec3 aPos;
        layout (location = 1) in vec4 aColor;

        out vec4 vColor;

        uniform mat4 model;

        void main()
        {
            gl_Position = model * vec4(aPos, 1.0f);
            vColor = aColor;
        }
        """
    fragSrc:cstring = """
        #version 330 core
        out vec4 FragColor;    
        in vec4 vColor;

        void main()
        {
            FragColor = vColor;
        }

        """
# opengl stuff for sending the shader text and the vertices.
proc send_src(vert:var cstring, frag:var cstring):OGLuint =
    var success:OGLint
    # vertex
    var vertexShader = glCreateShader(GL_VERTEX_SHADER)
    glShaderSource(vertexShader, 1, addr vert, nil)
    glCompileShader(vertexShader)
    # Check compilation errors.
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, addr success)
    if bool(success) == false:
        echo(" vertex shader compilation failed (send_src)")
    else:
        echo("vertexShader compiled (send_src)")

    # fragment
    var fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
    glShaderSource(fragmentShader, 1, addr frag, nil)
    glCompileShader(fragmentShader)
    # Check compilation errors.
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, addr success)
    if bool(success) == false:
        echo("fragment shader compilation failed (send_src)")
    else:
        echo("fragmentShader compiled (send_src)")

    # Shader program
    result = glCreateProgram()
    glAttachShader(result, vertexShader)
    glAttachShader(result, fragmentShader)
    glLinkProgram(result)
    # Check for linkage errors.
    glGetProgramiv(result, GL_LINK_STATUS, addr success)
    if success == 0:
        echo("program linking failed (send_src)") 
    else:
        echo("shader linked (send_src)")

    glDeleteShader(vertexShader)
    glDeleteShader(fragmentShader)

glViewport(0, 0, WINDOW_W, WINDOW_H)
shadID = send_src(vertSrc, fragSrc)

var VAO, VBO, EBO:OGLuint
glGenVertexArrays(1, addr VAO)
glGenBuffers(1, addr VBO)
glGenBuffers(1, addr EBO)
glBindVertexArray(VAO)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.len * sizeof(OGLfloat), 
             addr vertices[0], GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.len * sizeof(OGLuint), 
             addr indices[0], GL_STATIC_DRAW)
# Position layout
glVertexAttribPointer(0, POSITION_LENGTH, GL_FLOAT, GL_FALSE, (POSITION_LENGTH + COLOR_LENGTH) * OGLint(sizeof(OGLfloat)), 
                      nil)
glEnableVertexAttribArray(0)
# Color layout
glVertexAttribPointer(1, COLOR_LENGTH, GL_FLOAT, GL_FALSE, (POSITION_LENGTH + COLOR_LENGTH) * OGLint(sizeof(OGLfloat)), 
                      cast[pointer](colorDataOffset))
glEnableVertexAttribArray(1)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
glUseProgram(shadID)
# The render loop.
while bool(glfwWindowShouldClose(winHandle)) == false:
    glClearColor(0.2, 0.3, 0.3, 1.0)
    glClear(GL_COLOR_BUFFER_BIT)
    glBindVertexArray(VAO)
    glUniformMatrix4fv(glGetUniformLocation(shadID, "model"), 1, char(false), addr modelMatrix[0])
    glDrawElements(GL_TRIANGLES, OGLint(indices.len), GL_UNSIGNED_INT, nil)
    glfwSwapBuffers(winHandle)
    glfwPollEvents()

glDeleteVertexArrays(1, addr VAO)
glDeleteBuffers(1, addr VBO)
glDeleteBuffers(1, addr EBO)
glfwDestroyWindow(winHandle)
glfwTerminate()

And here's the result.

enter image description here

I wish you a happy holiday season.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Ploumploum
  • 311
  • 1
  • 9

1 Answers1

4

Since the viewport is rectangular you have to take into account the aspect ratio of the viewport.

Calculate the aspect ration (aspect) of the viewport, which is the width of the viewport divided by its height.

Apply a simple view matrix, to the transformation. The view matrix is a simply scaling of the x axis, by the reciprocal aspect ratio (1.0/aspect).

It is sufficient to move the quad to the enter of the screen first and rotate it then:

modelMatrix = scaling(1.0/aspect, 1.0) * # aspect ratio
              rotation2D(45.0) *         # rotation on Z axis
              translation(0.5, -0.5) *   # move to the center of the screen
              scaling(1.0, 1.0)          # change nothing with these values.

Note, according to the matrix initialization and multiplication operator a transformation followed by a rotation has to be transformed as follows:

model = rotate * translate(-pivot_x, -pivot_y)  

See GLSL Programming/Vector and Matrix Operations.

Preview:


Note alternatively you can add a separated (orthographic) projection matrix to the shader:

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec4 aColor;

out vec4 vColor;

uniform mat4 project;
uniform mat4 model;

void main()
{
    gl_Position = project * model * vec4(aPos, 1.0f);
    vColor = aColor;
}

projMatrix = scaling(1.0/aspect, 1.0)
modelMatrix = rotation2D(45.0) *         # rotation on Z axis
              translation(0.5, -0.5) *   # move to the center of the screen
              scaling(1.0, 1.0)          # change nothing with these values.

glUniformMatrix4fv(glGetUniformLocation(shadID, "project"), 1, char(false), addr projMatrix[0])
glUniformMatrix4fv(glGetUniformLocation(shadID, "model"), 1, char(false), addr modelMatrix[0])

If you want to rotate around a pivot (pivot_x, pivot_y), then you have to

model = translate(pivot_x, pivot_y) * rotate * translate(-pivot_x, -pivot_y) 

e.g. pivot (-0.5, 0.5)

modelMatrix = translation(-0.5, 0.5) *   # pivot
              rotation2D(45.0) *         # rotation on Z axis
              translation(0.5, -0.5) *   # inverted pivot
              scaling(1.0, 1.0)         

Preview:


If you finally want to move the pivot of the quad to the center of the screen, then you have to:

model = 
    translate(widht/2 - pivot_x, height/2 - pivot_y) *
    translate(pivot_x, pivot_y) * rotate * translate(-pivot_x, -pivot_y) 

e.g.

modelMatrix = translation(float(WINDOW_W/2)-100, float(WINDOW_H/2)-100) * 
              translation(100, 100) *
              rotation2D(45.0) *
              translation(-100, -100) *
              scaling(1.0, 1.0)

Which is the same as:

model = translate(widht/2, height/2) * rotate * translate(-pivot_x, -pivot_y) 

modelMatrix = translation(float(WINDOW_W/2), float(WINDOW_H/2)) * 
              rotation2D(45.0) *
              translation(-100, -100) *
              scaling(1.0, 1.0)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Thank you for the answer but I want to do transformations relatively to the center of the quad. I want to choose an origin point. Like in the learnopgl tutorial. And if I want to translate the quad by 1.0, 1.0 https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites . My example code was too simplified and don't reflect what I exactly want, sorry for the misunderstanding. – Ploumploum Dec 26 '18 at 21:49
  • Sorry for the broken sentence in the previous post, I can't edit anymore. – Ploumploum Dec 26 '18 at 22:15
  • My real project has a orthogonal projection who already solves the aspect ratio problem. It was removed in my example code to reduce the size of the text. My blue square is displayed too low and I have hard time to find the good sequence of transformations. I will share a more complete example when I found some hosting. – Ploumploum Dec 27 '18 at 12:48
  • I pasted my code here https://bitbucket.org/Neotry/2d-rendering-test./src/master/WIPtest.nim. "c_libs.o" just contains glfw3 and glew includes. The aspect ratio is replaced by the ortho projection but the result is not expected. – Ploumploum Dec 27 '18 at 13:14
  • Yes projection, view, and model matrices are in the shader. I'm able to rotate the quad around an origin point, but I don't know how to translate it to screen center. Sorry for the link, that code viewer was set to private by default, but that's should works now. – Ploumploum Dec 27 '18 at 13:27
  • No rotation on the quad center only and it's works fine. But when I translate to some point of the "world", the screen center as example, the posiiton is wrong (but rotation is still good). – Ploumploum Dec 27 '18 at 13:34
  • @Ploumploum See the extension to the answer. – Rabbid76 Dec 27 '18 at 13:48