OpenGL 简介:通过示例代码探索图形绘制
2024-02-21 20:51:34
在初步了解 OpenGL 的基本概念后,我们自然会产生疑问:OpenGL 究竟是如何绘制图形的?它内部的流程是什么样的?为了解答这些疑问,我们不妨通过两个简单的例子来深入理解 OpenGL 的 API 和图形绘制流程。
首先,我们来绘制一个最基础的图形——三角形。在 OpenGL 中,所有的图形都是由一系列的顶点构成的,三角形自然也不例外。我们需要先定义三角形的三个顶点坐标,然后告诉 OpenGL 如何利用这些顶点数据绘制出三角形。
void drawTriangle() {
// 首先,定义三角形的三个顶点坐标,每个顶点由 x、y、z 三个坐标值组成
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 左下角顶点
0.5f, -0.5f, 0.0f, // 右下角顶点
0.0f, 0.5f, 0.0f // 顶部顶点
};
// 创建一个顶点数组对象 (VAO),VAO 可以理解为一个容器,用于存储与顶点相关的所有信息
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// 创建一个顶点缓冲对象 (VBO),VBO 用于存储顶点数据
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// 将顶点数据复制到 VBO 中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告诉 OpenGL 如何解释 VBO 中的顶点数据
// 0 表示顶点属性的位置,3 表示每个顶点有 3 个 float 值,GL_FLOAT 表示数据类型为 float,
// GL_FALSE 表示不需要归一化,3 * sizeof(float) 表示每个顶点的步长,(void*)0 表示偏移量为 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 最后,调用 glDrawArrays 函数绘制三角形
// GL_TRIANGLES 表示绘制模式为三角形,0 表示从第 0 个顶点开始绘制,3 表示绘制 3 个顶点
glDrawArrays(GL_TRIANGLES, 0, 3);
}
通过这段代码,我们成功地绘制了一个三角形。接下来,我们尝试绘制一个稍微复杂一点的图形——立方体。立方体由 6 个面组成,每个面都是一个正方形,而每个正方形又可以由两个三角形组成。因此,我们可以将立方体看作是由 12 个三角形组成的。
void drawCube() {
// 定义立方体的顶点数据,每个顶点仍然由 x、y、z 三个坐标值组成
float vertices[] = {
// 前面
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
// 后面
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
// ... 其他面的顶点数据 ...
};
// 定义索引数据,用于告诉 OpenGL 如何使用顶点数据绘制三角形
GLuint indices[] = {
0, 1, 2, 2, 3, 0, // 前面
4, 5, 6, 6, 7, 4, // 后面
// ... 其他面的索引数据 ...
};
// 创建 VAO 和 VBO,并将顶点数据复制到 VBO 中
GLuint vao, vbo;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &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);
// 创建一个索引缓冲对象 (EBO),EBO 用于存储索引数据
GLuint ebo;
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 调用 glDrawElements 函数绘制立方体
// GL_TRIANGLES 表示绘制模式为三角形,36 表示绘制 36 个索引,GL_UNSIGNED_INT 表示索引数据类型为 unsigned int,(void*)0 表示偏移量为 0
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
}
通过这两个例子,我们可以总结出 OpenGL 的图形绘制流程:
- 定义顶点数据 : 包括顶点位置、颜色、法线、纹理坐标等信息。
- 创建 VAO : VAO 用于存储与顶点相关的所有信息,例如 VBO、EBO 和顶点属性指针等。
- 创建 VBO : VBO 用于存储顶点数据。
- 将顶点数据复制到 VBO : 使用
glBufferData
函数将顶点数据从 CPU 内存复制到 GPU 内存。 - 设置顶点属性指针 : 告诉 OpenGL 如何解释 VBO 中的顶点数据。
- 创建 EBO (可选) : 如果使用索引绘制,则需要创建 EBO 并将索引数据复制到 EBO 中。
- 调用
glDrawArrays
或glDrawElements
函数渲染几何体 :glDrawArrays
用于非索引绘制,glDrawElements
用于索引绘制。
常见问题解答
1. VAO、VBO 和 EBO 分别是什么?
VAO (Vertex Array Object) 可以理解为一个容器,用于存储与顶点相关的所有信息,例如 VBO、EBO 和顶点属性指针等。VBO (Vertex Buffer Object) 用于存储顶点数据,例如顶点位置、颜色、法线、纹理坐标等。EBO (Element Buffer Object) 用于存储索引数据,索引数据用于告诉 OpenGL 如何使用顶点数据绘制三角形。
2. 为什么要使用 VAO?
使用 VAO 可以将与顶点相关的所有信息存储在一个对象中,方便管理和切换不同的顶点配置。例如,如果需要绘制多个不同的物体,每个物体都有不同的顶点数据和顶点属性指针,那么可以使用多个 VAO,每个 VAO 存储一个物体的顶点信息。
3. 为什么要使用 EBO?
使用 EBO 可以减少顶点数据的冗余,提高渲染效率。例如,一个立方体有 8 个顶点,但如果使用索引绘制,只需要存储 8 个顶点数据和 36 个索引数据,而如果使用非索引绘制,则需要存储 24 个顶点数据。
4. glVertexAttribPointer
函数的参数分别是什么含义?
glVertexAttribPointer
函数用于设置顶点属性指针,告诉 OpenGL 如何解释 VBO 中的顶点数据。它的参数含义如下:
index
: 顶点属性的位置。size
: 每个顶点属性的组成部分数量,例如顶点位置由 x、y、z 三个组成部分,因此size
为 3。type
: 数据类型,例如GL_FLOAT
、GL_INT
等。normalized
: 是否需要归一化。stride
: 每个顶点的步长,单位为字节。pointer
: 偏移量,单位为字节。
5. glDrawArrays
和 glDrawElements
函数的区别是什么?
glDrawArrays
函数用于非索引绘制,glDrawElements
函数用于索引绘制。非索引绘制需要为每个三角形指定三个顶点,而索引绘制可以使用索引数据来复用顶点数据,减少顶点数据的冗余。