在OpenGL中绕着旋转中心进行旋转

3
我试图在我的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()

这里是结果。

enter image description here

祝您节日快乐。

1个回答

4

由于视口是矩形的,因此必须考虑视口的宽高比。

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

应用简单的视图矩阵进行转换。视图矩阵是通过倒数宽高比(1.0/aspect)对x轴进行简单缩放。

首先将四边形移动到屏幕中心,然后再旋转即可:

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编程/向量和矩阵操作

预览:


另外,您可以将一个单独的(正交)投影矩阵添加到着色器中:

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

谢谢您的回答,但我想相对于四边形的中心进行变换。我想选择一个原点。就像在learnopgl教程中一样。如果我想将四边形平移1.0、1.0 https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites 。我的示例代码过于简化,不能反映出我确切的意图,对此我感到抱歉。 - Ploumploum
抱歉上一篇帖子中有断句,我无法再进行编辑了。 - Ploumploum
我的真实项目有一个正交投影,已经解决了纵横比问题。为了减小文本的大小,它在我的示例代码中被移除了。我的蓝色正方形显示得太低了,我很难找到正确的变换顺序。当我找到一些托管时,我会分享一个更完整的示例。 - Ploumploum
是的,投影、视图和模型矩阵都在着色器中。我能够围绕原点旋转四边形,但不知道如何将其平移到屏幕中心。抱歉链接有问题,代码查看器默认设置为私有,但现在应该可以使用了。 - Ploumploum
没有在四边形中心进行旋转,它可以正常工作。但是当我将其翻译到某个“世界”点,例如屏幕中心时,位置就会出错(但旋转仍然很好)。 - Ploumploum
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接