如何将OpenGL+GLFW渲染的坐标原点从左下角改为左上角?
实现OpenGL+GLFW渲染内容锚定窗口左上角
我正在用OpenGL和GLFW开发文本编辑器,传统GUI应用以左上角为坐标原点(而非OpenGL默认的左下角),所以调整坐标原点至关重要。我需要实现窗口调整大小时,渲染内容始终锚定左上角,而非默认的左下角。
效果对比
初始窗口:
调整后窗口(当前默认锚定左下角):
期望的调整后窗口效果(锚定左上角):
最小复现代码
主程序代码
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> #include <string> #include <fstream> #include <sstream> class Shader { public: unsigned int ID; // constructor generates the shader on the fly // ------------------------------------------------------------------------ Shader(const char* vertexPath, const char* fragmentPath) { // 1. retrieve the vertex/fragment source code from filePath std::string vertexCode; std::string fragmentCode; std::ifstream vShaderFile; std::ifstream fShaderFile; // ensure ifstream objects can throw exceptions: vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); try { // open files vShaderFile.open(vertexPath); fShaderFile.open(fragmentPath); std::stringstream vShaderStream, fShaderStream; // read file's buffer contents into streams vShaderStream << vShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf(); // close file handlers vShaderFile.close(); fShaderFile.close(); // convert stream into string vertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); } catch (std::ifstream::failure& e) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl; } const char* vShaderCode = vertexCode.c_str(); const char * fShaderCode = fragmentCode.c_str(); // 2. compile shaders unsigned int vertex, fragment; // vertex shader vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vShaderCode, NULL); glCompileShader(vertex); checkCompileErrors(vertex, "VERTEX"); // fragment Shader fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fShaderCode, NULL); glCompileShader(fragment); checkCompileErrors(fragment, "FRAGMENT"); // shader Program ID = glCreateProgram(); glAttachShader(ID, vertex); glAttachShader(ID, fragment); glLinkProgram(ID); checkCompileErrors(ID, "PROGRAM"); // delete the shaders as they're linked into our program now and no longer necessary glDeleteShader(vertex); glDeleteShader(fragment); } // activate the shader // ------------------------------------------------------------------------ void use() { glUseProgram(ID); } // utility uniform functions // ------------------------------------------------------------------------ void setBool(const std::string &name, bool value) const { glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); } // ------------------------------------------------------------------------ void setInt(const std::string &name, int value) const { glUniform1i(glGetUniformLocation(ID, name.c_str()), value); } // ------------------------------------------------------------------------ void setFloat(const std::string &name, float value) const { glUniform1f(glGetUniformLocation(ID, name.c_str()), value); } // ------------------------------------------------------------------------ void setMat4(const std::string &name, const float* value) const { glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, value); } private: // utility function for checking shader compilation/linking errors. // ------------------------------------------------------------------------ void checkCompileErrors(unsigned int shader, std::string type) { int success; char infoLog[1024]; if (type != "PROGRAM") { glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; } } else { glGetProgramiv(shader, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader, 1024, NULL, infoLog); std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; } } } }; void processInput(GLFWwindow *window); void framebuffer_size_callback(GLFWwindow* window, int width, int height); const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; int screenWidth = SCR_WIDTH; int screenHeight = SCR_HEIGHT; int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } Shader ourShader("3.3.shader.vs", "3.3.shader.fs"); // 改用像素坐标示例:三角形左上角锚定(0,0),向下延伸100像素,向右延伸100像素 float vertices[] = { 100.0f, 100.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right 0.0f, 100.0f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left 50.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // top (左上角位置) }; unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); while (!glfwWindowShouldClose(window)) { processInput(window); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ourShader.use(); // 手动构造正交投影矩阵:原点在左上角,X向右递增,Y向下递增 float projMatrix[16] = { 2.0f / screenWidth, 0.0f, 0.0f, -1.0f, 0.0f, -2.0f / screenHeight, 0.0f, 1.0f, 0.0f, 0.0f, -2.0f / (1.0f - (-1.0f)), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; ourShader.setMat4("projection", projMatrix); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glfwTerminate(); return 0; } void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { screenWidth = width; screenHeight = height; glViewport(0, 0, width, height); }
片段着色器(3.3.shader.fs)
#version 330 core out vec4 FragColor; in vec3 ourColor; void main() { FragColor = vec4(ourColor, 1.0f); }
修改后的顶点着色器(3.3.shader.vs)
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; out vec3 ourColor; uniform mat4 projection; void main() { gl_Position = projection * vec4(aPos, 1.0); ourColor = aColor; }
核心修改说明
- 顶点着色器加入投影矩阵:通过
uniform mat4 projection接收外部传入的坐标变换矩阵,将顶点坐标转换为以左上角为原点的NDC坐标。 - 窗口大小回调:监听窗口尺寸变化,实时更新视口和投影矩阵参数。
- 构造正交投影矩阵:手动构造(或用GLM库)翻转Y轴的正交矩阵,将窗口左上角设为坐标原点,X轴向右,Y轴向下,完全匹配传统GUI的坐标系统。
- 调整顶点坐标:改用像素坐标(或归一化坐标),让渲染内容的位置直接对应窗口中的实际位置,窗口缩放时自动锚定左上角。
内容的提问来源于stack exchange,提问作者xXxAirSnifferxXx
相关产品推荐
相关产品推荐

