OpenGL(三)管线介绍和三角形绘制

一、图形渲染管线概述

在OpenGL中,图形渲染管线(Graphics Pipeline)是将3D坐标转换为屏幕2D像素的核心处理流程。整个过程分为两个主要阶段:

  1. 顶点处理阶段:将3D坐标转换为标准化设备坐标(Normalized Device Coordinates, NDC)
  2. 片段处理阶段:将2D坐标转换为实际像素颜色

管线阶段详解

  1. 顶点着色器(Vertex Shader)
  • 处理单个顶点
  • 核心任务:3D坐标 → 标准化设备坐标
  • 可自定义的着色器程序
  1. 几何着色器(Geometry Shader,可选)
  • 处理图元(点/线/三角形)
  • 生成新几何体
  1. 图元装配(Primitive Assembly)
  • 将顶点组装为指定图元
  1. 光栅化(Rasterization)
  • 将图元映射为屏幕像素
  • 生成片段(Fragment)
  1. 片段着色器(Fragment Shader)
  • 计算像素最终颜色
  • 处理光照/阴影等高级效果
  1. 测试与混合(Tests and Blending)
  • 深度测试(Depth Test)
  • 透明度混合(Alpha Blending)

二、核心概念:VAO、VBO、EBO

2.1 顶点缓冲对象(Vertex Buffer Object, VBO)

  • 作用:在GPU显存中存储顶点数据
  • 优势:减少CPU到GPU的数据传输次数
  • 创建流程:
  unsigned int VBO;
  glGenBuffers(1, &VBO);          // 生成VBO
  glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 传输数据

2.2 顶点数组对象(Vertex Array Object, VAO)

  • 作用:存储顶点属性配置
  • 优势:简化状态管理
  • 创建流程:
  unsigned int VAO;
  glGenVertexArrays(1, &VAO);     // 生成VAO
  glBindVertexArray(VAO);         // 绑定
  // 配置顶点属性指针...

2.3 元素缓冲对象(Element Buffer Object, EBO / IBO)

  • 作用:存储顶点索引,避免重复顶点数据
  • 优势:减少内存占用
  • 创建流程:
  unsigned int EBO;
  glGenBuffers(1, &EBO); 
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

三、绘制三角形完整实现

3.1 顶点数据定义

float vertices[] = {
    -0.5f, -0.5f, 0.0f,  // 左下角
     0.5f, -0.5f, 0.0f,  // 右下角
     0.0f,  0.5f, 0.0f   // 顶部
};

3.2 顶点着色器(vertex_shader.glsl)

#version 330 core
layout (location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0); // 直接传递坐标
}

3.3 片段着色器(fragment_shader.glsl)

#version 330 core
out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 固定橘色
}

3.4 着色器编译与链接

// 编译顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

// 编译片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

// 创建着色器程序
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

// 删除临时着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

3.5 顶点属性配置

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 配置位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

3.6 渲染循环

while (!glfwWindowShouldClose(window)) {
    processInput(window);

    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形

    glfwSwapBuffers(window);
    glfwPollEvents();
}

四、使用EBO绘制矩形

4.1 顶点与索引数据

float vertices[] = {
     0.5f,  0.5f, 0.0f,  // 右上
     0.5f, -0.5f, 0.0f,  // 右下
    -0.5f, -0.5f, 0.0f,  // 左下
    -0.5f,  0.5f, 0.0f   // 左上
};

unsigned int indices[] = {
    0, 1, 3,  // 第一个三角形
    1, 2, 3   // 第二个三角形
};

4.2 EBO配置

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

4.3 绘制调用

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

五、线框模式与调试技巧

5.1 开启线框模式

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

5.2 调试着色器

// 检查编译错误
int success;
char infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
    glGetShaderInfoLog(shader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}

六、效果图

七、完整代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// 设置窗口尺寸
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

// 顶点着色器源码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

// 片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
    // 初始化并配置 GLFW
    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

    // 创建 GLFW 窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "创建 GLFW 窗口失败" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // 初始化 GLAD,加载 OpenGL 函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "GLAD 初始化失败" << std::endl;
        return -1;
    }

    // 创建并编译着色器程序
    // 顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // 检查顶点着色器编译错误
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "错误::着色器::顶点::编译失败\n" << infoLog << std::endl;
    }

    // 片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // 检查片段着色器编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "错误::着色器::片段::编译失败\n" << infoLog << std::endl;
    }

    // 链接着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // 检查着色器程序链接错误
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "错误::着色器::程序::链接失败\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // 设置顶点数据(以及缓冲区)并配置顶点属性
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // 左下角
         0.5f, -0.5f, 0.0f, // 右下角
         0.0f,  0.5f, 0.0f  // 顶部
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    // 绑定 VAO(顶点数组对象)
    glBindVertexArray(VAO);

    // 绑定 VBO(顶点缓冲对象)并传输数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 配置顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // 解绑 VBO(可以安全解绑)
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // 解绑 VAO(一般情况下不解绑)
    glBindVertexArray(0);

    // 渲染循环
    while (!glfwWindowShouldClose(window))
    {
        // 处理输入
        processInput(window);

        // 渲染
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 绘制三角形
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // glBindVertexArray(0); // 不需要每次都解绑

        // 交换缓冲区并处理事件
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 释放资源
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // 关闭 GLFW,释放资源
    glfwTerminate();
    return 0;
}

// 处理输入:查询 GLFW 是否检测到按键按下
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)
{
    // 重新设置 OpenGL 视口大小
    glViewport(0, 0, width, height);
}
© 版权声明
THE END
喜欢就支持一下吧
点赞36
评论 抢沙发

请登录后发表评论

    暂无评论内容