为什么旋转矩阵不在文本中心旋转?

Why doesn't the rotation matrix rotate in the center of the text?

我正在尝试在 OpenGL 中旋转文本,但是,问题是文本按应有的方式旋转但在错误的点,即它没有在文本的中心旋转(而且我想知道我是怎么做到的!)。

我之所以感到困惑,是因为我对文本的不同字母使用了多个纹理,这让我感到困惑。

如果您想知道我使用的是哪个库,那就是 CGLM

但是当我编译这段代码时,rotate 函数以错误的旋转点旋转对象。

根据 Rabbid76。我做了以下得到整个文本的大小:-

for (int i = 0; i < (signed)strlen(text); i++)
{
    tw += iterator[(int)text[i]].Character_Array.Size[0];
    th += iterator[(int)text[i]].Character_Array.Size[1];
}

我仍然没有得到正确的结果,文本仍然错位。

在任何轮换之前(这是它应该轮换的地方):- 旋转 45 度后(从其位置错位):-

编辑:源文件的全部代码:-

// Std. Includes
#include <stdio.h>
#include <stdlib.h>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GLM
#include <cglm/cglm.h>
// FreeType
#include <ft2build.h>
#include FT_FREETYPE_H

// Properties
const GLuint WIDTH = 800, HEIGHT = 600;

const GLchar * vertexShaderSource =
    "#version 330 core\n"
    "layout(location = 0) in vec4 vertex;\n"
    "out vec2 TexCoords;\n"
    "uniform mat4 projection;\n"
    "uniform mat4 model;\n"
    "void main()\n"
    "{\n"
    "gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);\n"
    "TexCoords = vertex.zw;\n"
    "}\n[=11=]";

const GLchar * fragmentShaderSource =
    "#version 330 core\n"
    "in vec2 TexCoords;\n"
    "out vec4 color;\n"
    "uniform sampler2D text;\n"
    "uniform vec4 textColor;\n"
    "void main()\n"
    "{\n"
    "vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);\n"
    "color = textColor * sampled;\n"
    "}\n[=11=]";

/// Holds all state information relevant to a character as loaded using FreeType
typedef struct {
    GLuint TextureID;   // ID handle of the glyph texture
    int Size[2];    // Size of glyph
    int Bearing[2];  // Offset from baseline to left/top of glyph
    GLuint Advance;    // Horizontal offset to advance to next glyph
} Character;

typedef struct
{
    GLchar char_Array;
    Character Character_Array;
} Iterator;

Iterator * iterator;

GLuint VAO, VBO;
// RenderText function.. to render our text...
void RenderText(GLuint program, const char * text, GLfloat originx, GLfloat originy, GLfloat x, GLfloat y, GLfloat scalex, GLfloat scaley, float rotation, float r, float g, float b, float a);

// The MAIN function, from here we start our application and run the Game loop
int main()
{
    iterator = NULL;
    // Init GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", NULL, NULL); // Windowed
    glfwMakeContextCurrent(window);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, WIDTH, HEIGHT);

    // Set OpenGL options
    glEnable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Compile and setup the shader
    GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertex_shader);
    GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragment_shader);
    GLuint program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);

    mat4 projection;
    glm_ortho(0.0f, (GLfloat)WIDTH, (GLfloat)HEIGHT, 0.0f, -1, 1, projection);
    glUseProgram(program);
    glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, (GLfloat *)projection);

    // FreeType
    FT_Library ft;
    // All functions return a value different than 0 whenever an error occurred
    if (FT_Init_FreeType(&ft))
        printf("ERROR::FREETYPE: Could not init FreeType Library\n");

    // Load font as face
    FT_Face face;
    if (FT_New_Face(ft, "playfair.ttf", 0, &face))
        printf("ERROR::FREETYPE: Failed to load font\n");

    // Set size to load glyphs as
    FT_Set_Pixel_Sizes(face, 0, 72);

    // Disable byte-alignment restriction
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    int i = 0;
    // Load first 128 characters of ASCII set
    for (GLubyte c = 0; c < 255; c++)
    {
        // Load character glyph 
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            printf("ERROR::FREETYTPE: Failed to load Glyph\n");
            continue;
        }
        // Generate texture
        GLuint texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RED,
            face->glyph->bitmap.width,
            face->glyph->bitmap.rows,
            0,
            GL_RED,
            GL_UNSIGNED_BYTE,
            face->glyph->bitmap.buffer
        );
        // Set texture options
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // Now store character for later use
        Character character = {
            texture,
            {(signed)face->glyph->bitmap.width, (signed)face->glyph->bitmap.rows},
            {face->glyph->bitmap_left, face->glyph->bitmap_top},
            (GLuint)face->glyph->advance.x
        };
        iterator = (Iterator*)realloc(iterator, sizeof(Iterator) * (i + 1));
        iterator[i].Character_Array = character;
        iterator[i].char_Array = c;
        i++;
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    // Destroy FreeType once we're finished
    FT_Done_Face(face);
    FT_Done_FreeType(ft);


    // Configure VAO/VBO for texture quads
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    glfwVulkanSupported();
    // Game loop
    float k = 0;
    while (!glfwWindowShouldClose(window))
    {
        // Check and call events
        glfwPollEvents();

        // Clear the colorbuffer
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        if (k > 360)
        {
            k = 0;
        }
        k += 0.1f;
        RenderText(program, "Rotation", 0, 0, 10.0f, 10.0f, 1.0f, 1.0f, k, 0.5f, 0.8f, 0.2f, 1.0f);

        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

void RenderText(GLuint program, const char * text, GLfloat originx, GLfloat originy, GLfloat x, GLfloat y, GLfloat scalex, GLfloat scaley, float rotation, float r, float g, float b, float a)
{
    // Activate corresponding render state  
    glUseProgram(program);
    glUniform4f(glGetUniformLocation(program, "textColor"), r, g, b, a);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);

    GLfloat tw = iterator[(int)text[i]].Character_Array.Bearing[0],
        th = 0.0f;
    for (int i = 0; i < (signed)strlen(text); i++)
    {
        Character ch = iterator[(int)text[i]].Character_Array;
        tw += (ch.Advance >> 6);
        th += ch.Size[1];
    }

    GLfloat rx = tw / 2.0f;
    GLfloat ry = th / 2.0f;

    mat4 model;
    glm_mat4_identity(model);

    glm_translate(model, (vec3) { originx, originy, 0.0f });

    glm_translate(model, (vec3) { scalex * rx, scaley * ry, 0.0f });
    glm_rotate(model, glm_rad(rotation), (vec3) { 0.0f, 0.0f, 1.0f });
    glm_translate(model, (vec3) { -scalex * rx, -scaley * ry, 0.0f });

    glm_scale(model, (vec3) { scalex, scaley, 1.0f });

    GLfloat xpos = 0.0;
    GLfloat ypos = 0.0;
    for (int i = 0; i < (signed)strlen(text); i++)
    {
        Character ch = iterator[(int)text[i]].Character_Array;

        mat4 ch_model;
        memcpy(ch_model, model, 16 * sizeof(float));
        glm_translate(ch_model, (vec3) { x, 0.0f, 0.0f });

        // Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (ch.Advance >> 6); // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))

        glUniformMatrix4fv(glGetUniformLocation(program, "model"), 1, GL_FALSE, (GLfloat *)ch_model);

        // Render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.TextureID);

        GLfloat w = ch.Size[0] * scalex;
        GLfloat h = ch.Size[1] * scaley;
        GLfloat vertices[6][4] = {
            { 0.0f,  h,     0.0, 1.0 },
            { w,     0.0f,  1.0, 0.0 },
            { 0.0f,  0.0f,  0.0, 0.0 },

            { 0.0f,  h,     0.0, 1.0 },
            { w,     h,     1.0, 01.0 },
            { w,     0.0f,  01.0, 0.0 },
        };

        // Update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // Render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

glm_rotate 在原点应用旋转。因此,如果您的 几何体的中心 不在原点,则不会围绕 object/geometry 的中心进行旋转。

解决方案:X = T(p)Rz(φ)T(−p) 其中 X 是最终变换,p 是枢轴

  1. 将枢轴(或对象中心,不是对象的位置,它不相同)平移到原点
  2. 应用旋转或缩放
  3. 将枢轴平移回其原始位置

如果您想围绕对象的中心旋转(如果对象的中心不在原点),那么您必须计算该对象的中心 object/geometry。在应用任何旋转之前。您可以计算边界框然后获取该框的中心。

cglm 也为此提供了函数:glm_vec_center(min, max, pivot); /* pivot = center of object */。现在我们有了中心点。让我们围绕那个点旋转:

/* ... */

glm_vec_center(min, max, pivot); /* center of object */
glm_vec_inv_to(pivot, pivotInv); /* -pivot */

/* ... */

glm_translate(model, pivot);
glm_rotate(model, angle, axis);
glm_translate(model, pivotInv);

上面的代码与 X = T(p)Rz(φ)T(−p)(平移(-pivot),旋转,平移(pivot) ).代码中的顺序似乎是相反的,因为 glm_translate = Transform * Translate。如果 glm_translate = Translate * Transform 则不会倒序。 cglm 将来可能也会提供这种替代翻译乘法

编辑: 通过启动 v0.4.2 版本 cglm 为此目的提供函数:检查 glm_rotate_at()glm_quat_rotate_at()glm_rotate_atmglm_quat_rotate_atmglm_rotate_atglm_quat_rotate_at 以来为枢轴点创建新的旋转旋转现有的变换。要使用这些功能,请确保您拥有最新版本。