👨🏻‍💻 编程

OpenGL 中 VAO 的概念

1. 顶点数组对象(VAO)概述

顶点数组对象(VAO,Vertex Array Object)是 OpenGL 中的一个重要概念,它是一个封装了顶点数据状态的对象。VAO 的作用是管理顶点数据(如位置、法线、纹理坐标等)的布局和如何将它们传递给着色器,简化了顶点数据的管理。

在 OpenGL 中,VAO 并不直接存储顶点数据,而是存储了一组关于如何从缓冲区(VBO)中读取顶点数据的状态配置。这意味着 VAO 记录了一个顶点数组的布局信息,并指定了每个顶点属性的缓冲区绑定情况。

2. VAO 的工作原理

顶点属性配置:VAO 保存了顶点属性指针(如位置、颜色、法线等)的设置,它并不保存实际的数据,而是记录了如何读取这些数据的信息。

顶点数据与 VAO 关联:通常,我们会先创建一个 VBO(Vertex Buffer Object),然后将这些 VBO 和顶点属性指针与一个 VAO 关联起来。VAO 会保留所有的属性指针和缓冲区绑定状态。

使用 VAO:当你要渲染一个对象时,只需要绑定 VAO,OpenGL 会自动根据 VAO 中的配置从对应的缓冲区中读取顶点数据并传递给着色器。

3. VAO 的使用步骤

1. 创建 VAO:首先创建一个 VAO 对象。

2. 绑定 VAO:将 VAO 绑定到当前的 OpenGL 状态中。

3. 配置顶点属性指针:使用 glVertexAttribPointer 配置顶点属性指针,指定如何解析 VBO 中的数据。

4. 启用顶点属性:使用 glEnableVertexAttribArray 启用指定的顶点属性。

5. 解绑 VAO 和 VBO:完成配置后,解绑 VAO 和 VBO,以便于管理和避免状态冲突。

4. 代码示例

以下是一个 OpenGL 渲染程序的示例,展示如何使用 VAO 和 VBO 来渲染一个简单的三角形:

1. 创建 VAO 和 VBO:

GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);   // 创建 VAO
glGenBuffers(1, &VBO);         // 创建 VBO

glBindVertexArray(VAO);        // 绑定 VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO);  // 绑定 VBO
float vertices[] = {
    // 位置                // 颜色
    0.0f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,  // 顶点1 (绿色)
   -0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,  // 顶点2 (红色)
    0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f   // 顶点3 (蓝色)
};

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  // 填充 VBO

// 设置顶点属性指针
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);  // 启用顶点颜色属性

glBindBuffer(GL_ARRAY_BUFFER, 0);  // 解绑 VBO

glBindVertexArray(0);  // 解绑 VAO

2. 渲染循环中的使用:

在渲染循环中,你只需绑定 VAO 并绘制即可:

glClear(GL_COLOR_BUFFER_BIT);  // 清空屏幕

// 绑定 VAO
glBindVertexArray(VAO);

// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);

glBindVertexArray(0);  // 解绑 VAO

3. 完整代码:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

// 顶点着色器代码
const char* vertexShaderSource = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
}
)";

// 片段着色器代码
const char* fragmentShaderSource = R"(
#version 330 core
in vec3 ourColor;
out vec4 FragColor;
void main()
{
    FragColor = vec4(ourColor, 1.0f);
}
)";

int main() {
    // 初始化 GLFW
    if (!glfwInit()) {
        std::cerr << "GLFW初始化失败!" << std::endl;
        return -1;
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL VAO Example", NULL, NULL);
    if (!window) {
        std::cerr << "窗口创建失败!" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glewInit();

    // 编译顶点着色器和片段着色器
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    // 创建 VAO 和 VBO
    GLuint VAO, VBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    float vertices[] = {
        0.0f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,  // 顶点1 (红色)
       -0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,  // 顶点2 (绿色)
        0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f   // 顶点3 (蓝色)
    };
    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);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // 渲染循环
    while (!glfwWindowShouldClose(window)) {
        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);
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    glfwTerminate();
    return 0;
}

5. 代码解释

1. 创建和绑定 VAO 和 VBO

• glGenVertexArrays(1, &VAO) 和 glGenBuffers(1, &VBO) 用于生成 VAO 和 VBO。

• glBindVertexArray(VAO) 绑定 VAO,这会激活 VAO,之后的顶点属性指针设置会保存在这个 VAO 中。

• glBindBuffer(GL_ARRAY_BUFFER, VBO) 绑定 VBO,然后用 glBufferData 将顶点数据传递给 GPU。

• 使用 glVertexAttribPointer 设置顶点属性指针,并通过 glEnableVertexAttribArray 启用这些属性。

2. 渲染循环

• glClear(GL_COLOR_BUFFER_BIT) 清空屏幕。

• glBindVertexArray(VAO) 绑定 VAO 后绘制三角形。

• glDrawArrays(GL_TRIANGLES, 0, 3) 绘制三角形,0 是从哪个顶点开始绘制,3 是绘制的顶点数。

3. 资源清理:使用 glDeleteVertexArrays 和 glDeleteBuffers 删除 VAO 和 VBO,避免内存泄漏。

6. 总结

VAO 是管理顶点数据和属性布局的对象,它简化了顶点属性的配置和绑定,使得渲染时只需要绑定 VAO 即可。

VBO 存储了顶点数据,而 VAO 存储了如何使用这些数据的配置信息,包括顶点属性指针的状态。

这样做的好处是,一旦配置好 VAO,之后只需要绑定 VAO 和绘制对象,不需要每次都重新设置顶点属性指针,大大简化了渲染过程。

留言

您的邮箱地址不会被公开。 必填项已用 * 标注