You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenGL使用GLM绕Z轴旋转2D三角形时旋转中心异常求助

问题

尝试绕Z轴旋转一个已通过质心计算确保居中的2D三角形,期望它绕窗口中心顺时针/逆时针旋转,但实际是绕最后定义的顶点旋转。以下是相关代码,已查阅矩阵和旋转教程仍未解决。

C++代码

// Third-party library
#include <SDL2/SDL.h>
// Include GLAD
#include <glad/glad.h>
#include <glm/vec3.hpp>
#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/string_cast.hpp>

// C++ Standard Libraries
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

// Screen dimensions
int gScreenHeight = 480;
int gScreenWidth  = 640;
SDL_Window* gGraphicsApplicationWindow = nullptr;
SDL_GLContext gOpenGLContext = nullptr;

// Main loop flag
bool gQuit = false;  // if true, we quit

GLuint gGraphicsPipelineShaderProgram = 0;

GLuint gVertexArrayObject = 0;
GLuint gVertexBufferObject  = 0;
GLuint gVertexBufferObject2 = 0;

glm::mat4 gVertexData(
    -0.4f, -0.4f,  0.0f, 0.0f,  // point 1 - bottom left
     0.4f, -0.4f,  0.0f, 0.0f,  // point 2 - bottom right
     0.0f,  0.8f,  0.0f, 0.0f,  // point 3 - top
     0.0f,  0.0f,  0.0f, 1.0f   // 4th dimension
);
const auto gVertexBytes = gVertexData.length() * gVertexData[0].length()
        * sizeof(gVertexData[0][0]);

std::string loadShaderAsString(const std::string& filename) {
    std::string result = "";
    std::string line = "";
    std::ifstream myFile(filename);
    if (myFile.is_open()) {
        while (std::getline(myFile, line)) {
            result += line + '\n';
        }
        myFile.close();
    }
    return result;
}

GLuint compileShader(GLuint type, const std::string& source) {
    GLuint shaderObject;
    if (type == GL_VERTEX_SHADER) {
        shaderObject = glCreateShader(GL_VERTEX_SHADER);
    } else if (type == GL_FRAGMENT_SHADER) {
        shaderObject = glCreateShader(GL_FRAGMENT_SHADER);
    }
    const char* src = source.c_str();
    glShaderSource(shaderObject, 1, &src, nullptr);
    glCompileShader(shaderObject);
    int result;
    glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, &length);
        char* errorMessages = new char[length];  // Could also use alloca here.
        glGetShaderInfoLog(shaderObject, length, &length, errorMessages);
       if (type == GL_VERTEX_SHADER) {
            std::cout << "ERROR: GL_VERTEX_SHADER compilation failed!\n"
                << errorMessages << "\n";
        } else if (type == GL_FRAGMENT_SHADER) {
            std::cout << "ERROR: GL_FRAGMENT_SHADER compilation failed!\n"
                << errorMessages << "\n";
        }
        delete[] errorMessages;
        glDeleteShader(shaderObject);
        return 0;
    }
    return shaderObject;
}

GLuint createShaderProgram(const std::string& vertexShaderSource,
                           const std::string& fragmentShaderSource) {
    GLuint programObject = glCreateProgram();
    GLuint myVertexShader   = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    GLuint myFragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
    glAttachShader(programObject, myVertexShader);
    glAttachShader(programObject, myFragmentShader);
    glLinkProgram(programObject);
    glValidateProgram(programObject);
    glDetachShader(programObject, myVertexShader);
    glDetachShader(programObject, myFragmentShader);
    glDeleteShader(myVertexShader);
    glDeleteShader(myFragmentShader);
    return programObject;
}

void createGraphicsPipeline() {
    std::string vertexShaderSource   = loadShaderAsString("../shaders/vert.glsl");
    std::string fragmentShaderSource = loadShaderAsString("../shaders/frag.glsl");
    gGraphicsPipelineShaderProgram = createShaderProgram(vertexShaderSource,
                                                        fragmentShaderSource);
}

void getOpenGLVersionInfo() {
    std::cout << "Vendor: " << glGetString(GL_VENDOR) << std::endl;
    std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl;
    std::cout << "Version: " << glGetString(GL_VERSION) << std::endl;
    std::cout << "Shading Language: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
}

void vertexSpecification() {
    const std::vector<GLfloat> vertexColors {
         1.0f,  0.0f,  0.0f,  // vertex 1 - Left
         0.0f,  1.0f,  0.0f,  // vertex 2 - Right
         0.0f,  0.0f,  1.0f   // vertex 3 - Top
    };
    glGenVertexArrays(1, &gVertexArrayObject);
    glBindVertexArray(gVertexArrayObject);
    glGenBuffers(1, &gVertexBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER,
                 gVertexBytes,
                 &gVertexData[0][0],
                 GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,
                          gVertexData[0].length(),
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          nullptr);

    glGenBuffers(1, &gVertexBufferObject2);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject2);
    glBufferData(GL_ARRAY_BUFFER,
                vertexColors.size() * sizeof(GL_FLOAT),
                vertexColors.data(),
                GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT,
                          GL_FALSE, 0, nullptr);
    glBindVertexArray(0);
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}

void initializeProgram() {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cout << "SDL could not be initialized: " <<
                  SDL_GetError();
        exit(1);
    }

    std::cout << "SDL video system is ready to go\n";

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    gGraphicsApplicationWindow = SDL_CreateWindow("C++ SDL2 Window",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            gScreenWidth,
            gScreenHeight,
            SDL_WINDOW_OPENGL);
    if (gGraphicsApplicationWindow == nullptr) {
        std::cout << "SDL WIndow was not able to be created" << std::endl;
        exit(1);
    }
    gOpenGLContext = SDL_GL_CreateContext(gGraphicsApplicationWindow);
    if (gOpenGLContext == nullptr) {
        std::cout << "OpenGL context not available" << std::endl;
        exit(1);
    }
    if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) {
        std::cout << "Glad was not initialized" << std::endl;
        exit(1);
    }
    getOpenGLVersionInfo();
}

void predraw() {
    static glm::vec3 zNorm(0.0f, 0.0f, 1.0f);
    gVertexData = glm::rotate(gVertexData, glm::radians(0.10f), zNorm);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER,
                 gVertexBytes,
                 &gVertexData[0][0],
                 GL_DYNAMIC_DRAW);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glViewport(0, 0, gScreenWidth, gScreenHeight);
    glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glUseProgram(gGraphicsPipelineShaderProgram);
}

void draw() {
    glBindVertexArray(gVertexArrayObject);
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBufferObject);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glUseProgram(0);
}

void mainLoop() {
    while (!gQuit) {
        predraw();
        draw();
        SDL_GL_SwapWindow(gGraphicsApplicationWindow);
    }
}

void cleanup() {
    SDL_DestroyWindow(gGraphicsApplicationWindow);
    SDL_Quit();
}

int main(int argc, char* argv[]) {
    initializeProgram();
    vertexSpecification();
    createGraphicsPipeline();
    mainLoop();
    cleanup();
    return 0;
}

顶点着色器

#version 410 core

layout(location=0) in vec4 position;
layout(location=1) in vec3 vertexColors;

out vec3 v_vertexColors;

void main()
{
   v_vertexColors = vertexColors;
   
   gl_Position = vec4(position.x, position.y, position.z, 1.0f);
}

片段着色器

#version 410 core

in vec3 v_vertexColors;

out vec4 color;

void main()
{
   color = vec4(v_vertexColors.r, v_vertexColors.g, v_vertexColors.b, 1.0f);
}
解决方案

问题根源

  1. 顶点存储方式错误:用glm::mat4存储顶点是误用类型,glm::mat4是4x4矩阵容器,不是顶点数据的存储结构,会导致旋转计算逻辑混乱。
  2. 旋转操作逻辑错误:glm::rotate的作用是生成旋转矩阵,而非直接对顶点数据进行旋转。直接将顶点数据的mat4传入该函数,相当于把顶点当作矩阵做变换,必然导致旋转中心偏移。

修复步骤

1. 修正顶点存储

改用std::vector<glm::vec4>存储顶点,替换原glm::mat4 gVertexData:

std::vector<glm::vec4> gVertexData = {
    glm::vec4(-0.4f, -0.4f, 0.0f, 1.0f), // point 1 - bottom left
    glm::vec4(0.4f, -0.4f, 0.0f, 1.0f),  // point 2 - bottom right
    glm::vec4(0.0f, 0.8f, 0.0f, 1.0f)    // point 3 - top
};
const auto gVertexBytes = gVertexData.size() * sizeof(glm::vec4);

2. 修改顶点着色器,添加旋转矩阵uniform

在着色器中接收旋转矩阵,通过矩阵乘法应用旋转:

#version 410 core

layout(location=0) in vec4 position;
layout(location=1) in vec3 vertexColors;

out vec3 v_vertexColors;
uniform mat4 rotationMatrix; // 新增旋转矩阵uniform

void main()
{
   v_vertexColors = vertexColors;
   
   gl_Position = rotationMatrix * position; // 应用旋转矩阵到顶点
}

3. 修正predraw函数,生成并传递旋转矩阵

不再直接修改顶点数据,而是生成旋转矩阵并传递给着色器:

void predraw() {
    static float angle = 0.0f;
    angle += 0.1f;
    // 生成绕Z轴的旋转矩阵,以坐标系原点(窗口中心)为旋转中心
    glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(0.0f, 0.0f, 1.0f));
    
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glViewport(0, 0, gScreenWidth, gScreenHeight);
    glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    
    glUseProgram(gGraphicsPipelineShaderProgram);
    // 获取uniform变量位置并传递矩阵数据
    GLuint rotationLoc = glGetUniformLocation(gGraphicsPipelineShaderProgram, "rotationMatrix");
    glUniformMatrix4fv(rotationLoc, 1, GL_FALSE, glm::value_ptr(rotationMatrix));
}

4. 修正vertexSpecification中的顶点属性指针

现在顶点是vec4类型,顶点属性分量数改为4:

glVertexAttribPointer(0,
                      4, // 替换原gVertexData[0].length()
                      GL_FLOAT,
                      GL_FALSE,
                      0,
                      nullptr);

原理说明

OpenGL中标准的旋转方式是通过矩阵变换实现的,旋转矩阵默认以坐标系原点(即你的窗口中心,因为顶点已居中)为旋转中心,完全符合需求。在着色器中应用矩阵变换比CPU端修改顶点数据更高效,尤其适合大量顶点的场景。

内容的提问来源于stack exchange,提问作者Jon G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:18:29