在macOS上学习OpenGL | VBO、VAO 和 EBO

数据科学与计算机学院 徐海洋

本学习基于 LearnOpenGL 。

OpenGL 的图形渲染管线(Graphics Pipeline)主要有两个主要任务:

  1. 把 3D 坐标转换成 2D 坐标;
  2. 把 2D 坐标转变为实际有颜色的像素。

现代的可编程之处在于下图所示的蓝色部分,可注入自定义的着色器。

在macOS上学习OpenGL | VBO、VAO 和 EBO_第1张图片

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

其用来管理 GPU 上存储顶点数据的内存(简单的数组)。

假设存在一组顶点数据:

float points[] = {
   0.0f,  0.5f,  0.0f,
   0.5f, -0.5f,  0.0f,
  -0.5f, -0.5f,  0.0f
};

如下为根据缓冲 ID 生成 VBO 对象,并绑定缓冲类型和复制顶点数据到缓冲的内存中。

GLuint points_vbo = 0;
glGenBuffers(1, &points_vbo);
glBindBuffer(GL_ARRAY_BUFFER, points_vbo);
glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), points, GL_STATIC_DRAW);
顶点数组对象(Vertex Array Object,VAO)

首先,一个顶点数组对象会存储以下这些内容:

  • glEnableVertexAttribArrayglDisableVertexAttribArray 的调用。
  • 通过 glVertexAttribPointer 设置的顶点属性配置。
  • 通过 glVertexAttribPointer 调用与顶点属性关联的顶点缓冲对象。

假设还存在一组颜色数据,同样地使用 VBO 来存储:

float colours[] = {
  1.0f, 0.0f,  0.0f,
  0.0f, 1.0f,  0.0f,
  0.0f, 0.0f,  1.0f
};

GLuint colours_vbo = 0;
glGenBuffers(1, &colours_vbo);
glBindBuffer(GL_ARRAY_BUFFER, colours_vbo);
glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colours, GL_STATIC_DRAW);

在macOS上学习OpenGL | VBO、VAO 和 EBO_第2张图片

然后,使用 glBindVertexArray 绑定 VAO 并描述顶点属性的布局:

GLuint vao = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

glBindBuffer(GL_ARRAY_BUFFER, points_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, colours_vbo);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

⚠️ glVertexAttribPointer 有六个参数,依次为:

  1. GLuint index:指定顶点属性的索引值。
  2. GLint size:每个顶点属性的组件数量(向量维数)。
  3. GLenum type:每个组件的数据类型。
  4. GLboolean normalized:是否将数据标准化(归一化)。
  5. GLsizei stride:指定连续两个顶点属性之间的步长(间隔)。
  6. const GLvoid * pointer:指定数据在缓冲中起始位置的偏移量(Offset)。

渲染结果如图所示:
在macOS上学习OpenGL | VBO、VAO 和 EBO_第3张图片

⚠️ 在本机实践中,对于 glVertexAttribPointer 的调用与下面等价(即渲染结果相同):

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);

⚠️ 这里只有三个顶点数据,但一个片段是对于每个平面的基于像素的区域而言的。值得注意的是,片段着色器会基于位置进行颜色的插值,更进一步说,任何顶点缓冲属性输入到片段着色器都会发生插值计算。

如果将位置和颜色数据合并在一起:

GLfloat vertices[] = {
    // vec3 position & vec3 color
    -0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,
    0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f
};

相应的 VBO 内存中的数据:
在macOS上学习OpenGL | VBO、VAO 和 EBO_第4张图片

那么 VAO 可以这样设置:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
索引缓冲对象(Element Buffer Object,EBO 或 Index Buffer Object,IBO)

使用 EBO 的索引绘制解决存储重复顶点的问题。

假设绘制来那个三角形来组成一个矩形:

  • 定义(不重复的)顶点和绘制所需的索引;
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开始! 
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};
  • 创建和绑定 EBO 后用 glBufferData 把索引复制到缓冲里;
unsigned int EBO;
glGenBuffers(1, &EBO);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  • glDrawElements 来替换 glDrawArrays 函数,来指明从索引缓冲渲染。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

最后的初始化和绘制代码现在看起来像这样:

// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);

总的来看 VBO、VAO 和 EBO 的结构关系,如图所示:
在macOS上学习OpenGL | VBO、VAO 和 EBO_第5张图片

你可能感兴趣的:(计算机图形学)