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); }
解决方案
问题根源
- 顶点存储方式错误:用
glm::mat4存储顶点是误用类型,glm::mat4是4x4矩阵容器,不是顶点数据的存储结构,会导致旋转计算逻辑混乱。 - 旋转操作逻辑错误:
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
相关产品推荐
相关产品推荐

