使用opengl围绕枢轴原点旋转

Rotating around the pivot origin with opengl

我尝试在我的 2D 图形程序中实现变换,但我遇到了一个谜(即使这是一个菜鸟话题,抱歉)。 我的示例代码尝试将四边形平移到屏幕中心,然后以中心为轴心旋转 45 度,但出了点问题。

矩阵运算的顺序取自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()

这是结果。

祝您节日快乐。

由于视口是矩形的,因此您必须考虑视口的纵横比。

计算视口的宽高比 (aspect),即视口的宽度除以其高度。

将一个简单的视图矩阵应用于转换。视图矩阵是 x 轴的简单缩放,按倒数纵横比 (1.0/aspect).

先将四边形移动到屏幕的入口处,然后再旋转就可以了:

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.

注意,根据矩阵初始化和乘法运算符,变换后旋转必须按如下方式变换:

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

参见 GLSL Programming/Vector and Matrix Operations

预览:


请注意,您也可以向着色器添加一个单独的(正交)投影矩阵:

#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])

如果你想围绕一个轴旋转(pivot_xpivot_y),那么你必须

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

例如枢轴 (-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)         

预览:


如果你最终想将四边形的轴心移动到屏幕的中心,那么你必须:

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

例如

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)

与以下相同:

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)