OpenGL中的VAO,VBO和EBO

OpenGL中的VAO,VBO和EBO

在OpenGL中,VBO(Vertex Buffer Object)、EBO(Element Buffer Object,也叫EBO)和 VAO(Vertex Array Object)是处理顶点数据的核心工具。

一. VBO(Vertex Buffer Object 顶点缓冲对象): 提高顶点数据传输效率

VBO 是一种存储顶点数据的缓冲对象,将顶点数据存储在 GPU 内存中,避免每帧重新上传数据。

存储在GPU中的数据,在渲染的时候,肯定是比存储在cpu中的快的。

使用场景

顶点数据不变(静态场景)或仅少量变化(动态场景)。用于绘制复杂模型(如几何体、网格)。

优点

数据存储在 GPU 内存中,访问速度更快。支持动态更新顶点数据(GL_DYNAMIC_DRAW)。提升渲染性能。

没有 VBO 的代码

早期的OpenGL版本,没有这种缓存模式的时候,顶点数据每次都通过 CPU 发送到 GPU,效率极低,如下所示:

void update()

{

glBegin(GL_TRIANGLES);

glVertex3f(0.0f, 0.5f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.0f);

glVertex3f(0.5f, -0.5f, 0.0f);

glEnd();

}

可以看到上面每次绘制的时候,都需要将顶点的数据传输一遍给GPU,这就比较慢了。

使用 VBO 的代码

顶点数据一次传输到 GPU,只需绑定缓冲即可绘制:

void init()

{

// 配置 VBO

unsigned int vbo;

// 创建1个顶点缓存

glGenBuffers(1, &vbo);

glBindBuffer(GL_ARRAY_BUFFER, vbo);

float vertices[] = {

0.0f, 0.5f, 0.0f,

-0.5f, -0.5f, 0.0f,

0.5f, -0.5f, 0.0f

};

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

}

// 每帧绘制

void update()

{

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glDrawArrays(GL_TRIANGLES, 0, 3);

}

使用了VBO之后,我们只需要在初始化的时候,把对应的顶点数据传输到GPU中的VBO就好了。

而动态场景使用 glBufferSubData 或 glMapBuffer 更新部分数据就好了。

二. EBO(Element Buffer Object 索引缓冲对象): 减少顶点冗余

EBO 用于存储顶点索引,避免重复存储相同顶点,减少内存消耗。

重复数据增加内存消耗,降低缓存命中率。EBO 仅适用于索引数据。

使用场景

绘制复杂网格模型,同一顶点会被多个图元共享,用索引来标记每个三角形用到哪个顶点,而不用相同顶点重复创建。

优点

节省内存。减少顶点处理次数,提升缓存效率。

没有 EBO 的代码

重复存储顶点数据:

float vertices[] = {

// 第一个三角形

0.0f, 0.5f, 0.0f,

-0.5f, -0.5f, 0.0f,

0.5f, -0.5f, 0.0f,

// 第二个三角形

0.5f, -0.5f, 0.0f,

1.0f, 0.0f, 0.0f,

0.0f, 0.5f, 0.0f

};

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

可以很清楚的看到,上面的例子中,两个三角形有一些共用的顶点,但是还是重新写了一遍,就很浪费,所以这时候就要EBO登场了。

使用 EBO 的代码

使用索引重用顶点数据:

float vertices[] = {

0.0f, 0.5f, 0.0f,

-0.5f, -0.5f, 0.0f,

0.5f, -0.5f, 0.0f,

1.0f, 0.0f, 0.0f

};

// 用索引来标记每个三角形用到的哪个顶点

unsigned int indices[] = {

0, 1, 2,

2, 3, 0

};

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

unsigned int ebo;

glGenBuffers(1, &ebo);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

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

用一个indices的数组指定了每个图元使用的顶点的顺序就好了,这样当在有大量重复顶点的时候,就很省了。

三. VAO(顶点数组对象): 减少状态切换,管理顶点属性配置

状态切换(如绑定缓冲和配置属性)在 GPU 渲染流水线中是高成本操作,VAO 可以将这些状态预先存储,避免多次切换。

VAO 本质上是状态记录器,是一种管理 OpenGL 顶点属性配置的对象,用于记录 VBO 和 EBO 的绑定状态及顶点属性设置,避免重复配置。(通俗点的讲,就是VAO只是个指针而已,实际干活的人还是VBO和EBO)

使用场景

场景复杂,有多个对象需要渲染时,使用 VAO 可以减少顶点属性配置的复杂度。多次绘制同一个对象,减少状态切换。

优点

减少状态切换。

将所有顶点属性配置打包到一个对象中,后续渲染时只需绑定 VAO 而不是重新配置,避免重复调用 glVertexAttribPointer。

2.简化代码。

管理多个 VBO、EBO,逻辑清晰。

3.提升性能。

减少 OpenGL 状态变更调用的次数。

没有 VAO 的代码

每次绘制都需要重新绑定缓冲对象并配置顶点属性:

void update()

{

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);

}

使用 VAO 的代码

VAO 保存了 VBO、EBO 的绑定状态和顶点属性配置,只需绑定 VAO 即可绘制:

void init()

{

// 配置 VAO(仅一次)

unsigned int vao;

glGenVertexArrays(1, &vao);

glBindVertexArray(vao);

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

glBindVertexArray(0);

}

void update()

{

// 每次绘制

glBindVertexArray(vao);

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);

glBindVertexArray(0);

}

四.总结

使用VBO、VEO和VAO,数据一次上传,多次使用, 顶点数据上传到显存后,可以在渲染的每一帧中重复使用,减少了CPU和GPU之间的通信。

而且使用索引可以减少冗余顶点数据,特别是在绘制复杂的几何图形或模型时显著提升效率。

代码上也变得更加简洁明了,绘制不同对象只需要切换对应的VAO就好了。

更多创意

后窗第108期:PPS被百度收购传言背后
365根据什么来封号

后窗第108期:PPS被百度收购传言背后

📅 10-16 🔥 6772
卫龙美味产品、渠道与成长展望分析
bt365无法登陆

卫龙美味产品、渠道与成长展望分析

📅 07-08 🔥 3175
侠客风云传弓箭排行:掌握远程射击的勇士之道
office365个人邮箱

侠客风云传弓箭排行:掌握远程射击的勇士之道

📅 07-03 🔥 6504