目录
一. 基本概念:
二. 理解缓冲对象
glVertex 函数
顶点数组(Vertex Array)
三. VBO(Vertex Buffer Object)顶点缓冲区对象
大体流程理解:
Qt 中使用QOpenGLWidget 的VBO 例子
四. VAO(Vertex Array Object)顶点数组对象
Qt 中使用QOpenGLWidget 的VAO 例子
五. EBO 索引缓冲对象
EBO示例
六. 总结
七. 参考链接:
一. 基本概念:
VAO(vertex-array object)顶点数组对象,用来管理VBO。
VBO(vertex buffer object)顶点缓冲对象,用来缓存用户传入的顶点数据。
EBO(element buffer object)索引缓冲对象,用来存放顶点索引数据。
这里的object指的都是GPU中的一块内存,每个内存对象都有不同的作用,但创建、绑定、数据传送等方式都比较类似,通过不同的类型加以区分,掌握了一种,其他的就很好理解。
二. 理解缓冲对象
glVertex 函数
最原始的设置顶点方法,在glBegin和glEnd之间使用。OpenGL3.0已经废弃此方法。每个glVertex与GPU进行一次通信,十分低效。
glBegin(GL_TRIANGLES);
glVertex(0, 0);
glVertex(1, 1);
glVertex(2, 2);
glEnd();
顶点数组(Vertex Array)
顶点数组也是收集好所有的顶点,一次性发送给GPU。不过数据不是存储于GPU中的,绘制速度上没有显示列表快,优点是可以修改数据。
显示列表和顶点数组都是过时的东西了,下面的VBO和VAO才是重点!
#define MEDIUM_STARS 40
M3DVector2f vMediumStars[MEDIUM_STARS];
//在这做点vMediumStars的设置//
glVertexPointer(2, GL_FLOAT, 0, vMediumStars);
glDrawArrays(GL_POINTS, 0, MEDIUM_STARS);
三. VBO(Vertex Buffer Object)顶点缓冲区对象
VBO,全称为Vertex Buffer Object,与FBO,PBO并称,但它实际上老不少。就某种意义来说,他就是VA(Vertex Array)的升级版。VBO出现的背景是人们发现VA和显示列表还有让人不满足的地方。一般,在OpenGL里,提高顶点绘制的办法:
(1)显示列表:把常规的glBegin()-glEnd()中的代码放到一个显示列表中(通常在初始化阶段完成),然后每遍渲染都调用这个显示列表。
(2)VA:使用顶点数组,把顶点以及顶点属性数据作为数组,渲染的时候直接用一个或几个函数调动这些数组里的数据进行绘制,形式上是减少函数调用的次数(告别glVertex),提高绘制效率。
但是,这两种方法都有缺点。VA是在客户端设置的,所以执行这类函数(glDrawArray或glDrawElement)后,客户端还得把得到的顶点数据向服务端传输一次(所谓的“二次处理”),这样一来就有了不必要的动作了,降低了效率——如果我们写的函数能直接把顶点数据发送给服务端就好了——这正是VBO的特性之一。显示列表的缺点在于它的古板,一旦设定就不容许修改,所以它只适合对一些“固定”的东西的绘制进行包装。(我们无办法直接在硬件层改顶点数据,因为这是脱离了流水线的事物)。而VBO直接把顶点数据交到流水线的第一步,与显示列表的效率还是有差距,但它这样就得到了操作数据的弹性——渲染阶段,我们的VBO绘制函数持续把顶点数据交给流水线,在某一刻我们可以把该帧到达了流水线的顶点数据取回客户端修改Vertexmapping),再提交回流水线(Vertex unmapping),或者用glBufferData或glBufferSubData重新全部或buffer提交修改了的顶点数据,这是VBO的另一个特性。
VBO结合了VA和显示列表这个说法不太妥当,应该说它结合了两者的一些特性,绘制效率在两者之间,且拥有良好的数据更改弹性。这种折衷造就了它一直为目前最高的地位。
通俗的解释:当我们在顶点着色器中把顶点传送到GPU中的时候,不能每个顶点数据传送一次,因为太耗时而且造成资源浪费,所以就要用到缓冲对象,我们把大量的数据存储在GPU内存上,然后一次传输大量数据到显卡上,顶点缓冲对象就是帮助我们来管理GPU内存的。
大体流程理解:
首先我们需要使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据,一定要使用该函数来配置各个属性的数据,因为顶点数据不只包含位置,还可能会包含顶点颜色、顶点法线等等,那一个顶点数据是如何被OpenGL解析然后放入到顶点着色器的各个属性中,就需要通过该函数进行准确的配置。
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVetexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的,因为同一个类型的缓冲区同时最多绑定一个目标缓冲。
下面一个简单的例子:
//创建VBO及VBO赋值
glGenBuffers(1, &m_nPositionVBO);
glBufferData(GL_ARRAY_BUFFER,
sizeof(posData), posData, GL_STREAM_DRAW);
glGenBuffers(1, &m_nTexcoordVBO);
glBufferData(GL_ARRAY_BUFFER,
sizeof(texData), texData, GL_STREAM_DRAW);
glGenBuffers(1, &m_nIndexVBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(indexData), indexData, GL_STATIC_DRAW);
//代码一,不使用shader VBO已经创建好了
glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, NULL);
//代码二,使用shader
glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
glEnableVertexAttribArray(VAT_POSITION);
glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
glEnableVertexAttribArray(VAT_TEXCOORD);
glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
glDisableVertexAttribArray(VAT_POSITION);
glDisableVertexAttribArray(VAT_TEXCOORD);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
glBindBuffer(GL_ARRAY_BUFFER, NULL);
Qt 中使用QOpenGLWidget 的VBO 例子
使用VBO之前必须调用create()创建。使用时,调用bind()。以告知OPenGL我们在使用的VBO。调用allocate()为VBO对象分配内存传递数据。有了数据还要告知OPenGL数据格式。VBO使用结束解除绑(养成一个好习惯);程序结束VBO自然没有用了,调用destroy()释放它。
数据存放在连续的内存中,要告知OPenGL如何解析数据。方法是调用:voidQOpenGLShaderProgram::setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)参数一:属性位置;参数三:偏移量。一个顶点的所有属性数据视为一组,offset指定某属性距该组最开始的字节数。最后一个参数:一个顶底的所有字节数。
数据准备好,着色器程序编译链接好,moel view projection 矩阵设定好,接着就可以绑定VBO进行绘制了。VBO用完就解绑了。
/头文件
#ifndef OPENGL_WIDGET_H
#define OPENGL_WIDGET_H
#include <QOpenGLFunctions>
#include <QOpenGLWidget>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
class OpenGLWidget : public QOpenGLWidget,
protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit OpenGLWidget(QWidget *parent = nullptr);
~OpenGLWidget();
protected:
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
private:
void makeObject();
private:
QOpenGLShaderProgram *m_program;
QOpenGLBuffer m_vbo;
int m_matrixUniform;
QMatrix4x4 m_pMat;
};
#endif // OPENGL_WIDGET_H
源文件
#include "opengl_widget.h"
OpenGLWidget::OpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent),
m_program(nullptr),
m_vbo(QOpenGLBuffer::VertexBuffer),
m_matrixUniform(0),
m_pMat()
{
}
OpenGLWidget::~OpenGLWidget()
{
m_vbo.destroy();
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
m_vbo.create();
m_program = new QOpenGLShaderProgram(this);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
":/vertex_shader.glsl");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
":/fragment_shader.glsl");
if (!m_program->link())
close();
m_matrixUniform = m_program->uniformLocation("matrix");
makeObject();
}
void OpenGLWidget::resizeGL(int w, int h)
{
float aspect = float(w)/float(h?h:1);
float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
m_pMat.setToIdentity();
m_pMat.perspective(fov, aspect, zNear, zFar);
}
void OpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
m_program->bind();
QMatrix4x4 mvMat;
mvMat.translate(0.0f, 0.0f, -3.0f);
m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
m_vbo.bind();
glDrawArrays(GL_TRIANGLES, 0, 3);
m_vbo.release();
m_program->release();
}
void OpenGLWidget::makeObject()
{
float arrVertex[] = {
// position color
0.0f, 0.707f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
};
m_vbo.bind();
m_vbo.allocate(arrVertex, sizeof(arrVertex));
int attr = -1;
attr = m_program->attributeLocation("posAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
attr = m_program->attributeLocation("colAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
m_vbo.release();
}
vertex 和frag
/v s//
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
attribute vec4 posAttr;
attribute vec4 colAttr;
varying vec4 col;
uniform mat4 matrix;
void main()
{
col = colAttr;
gl_Position = matrix * posAttr;
}
/f s//
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
varying vec4 col;
void main()
{
gl_FragColor = col;
}
四. VAO(Vertex Array Object)顶点数组对象
VAO并不是必须的,VBO可以独立使用,VBO缓存了数据,而数据的使用 方式(glVertexAttribPointer 指定的数据宽度等信息)并没有缓存,VBO将顶点信息放到GPU中,GPU在渲染时去缓存中取数据,二者中间的桥梁是GL-Context。GL-Context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,当切换VBO时(有多个VBO时,通过glBindBuffer切换 ),数据使用方式信息就丢失了。而GL-context就负责在他们之间进行切换。这也是为什么要在渲染过程中,在每份绘制代码之中会有glBindbuffer、glEnableVertexAttribArray、glVertexAttribPointer。VAO记录该次绘制所需要的所有VBO所需信息,把它保存到VBO特定位置,绘制的时候直接在这个位置取信息绘制。
VAO的全名是Vertex Array Object,首先,它不是Buffer-Object,所以不用作存储数据;其次,它针对“顶点”而言,也就是说它跟“顶点的绘制”息息相关。我们每一次绘制的时候,都需要绑定缓冲对象以此来拿到顶点数据,都需要去配置顶点属性指针以便OpenGL知道如何来解析顶点数据,这是相当麻烦的,对一个多边形而言,它每次的配置都是相同的,如何来存储这个相同的配置呢。
VAO为我们解决了这个大麻烦,当配置顶点属性数据的时候,只需要将配置函数调用执行一次,随后再绘制该物体的时候就只需要绑定相应的VAO即可,这样,我们就可以通过绑定不同的VAO(提醒,与VBO一样,同一时刻只能绑定一个VAO),使得在不同的顶点数据和属性配置切换变得非常简单。VAO记录的是一次绘制中所需要的信息,这包括“数据在哪里glBindBuffer”、“数据的格式是怎么样的glVertexAttribPointer”、shader-attribute的location的启用glEnableVertexAttribArray。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
VAO的使用非常简单,要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,配置完以后,VAO中就存储了我们想要的各种数据,之后解绑VAO供之后使用,再次使用需要我们再次绑定。
这里分析上面这个代码片段,并解释一下VAO和VBO的联系。
注意:glVertexAttribPointer()这个函数会自动从当前绑定的VBO里获取顶点数据,所以在第一节VBO里如果想要使用正确的顶点数据,每次都要绑定相应的VBO,但是现在,我们在绑定VBO之前绑定了VAO,那么glEnableVertexAttribPointer()所进行的配置就会保存在VAO中。我们可以通过不同的配置从一个VBO内拿到不同的数据放入不同的VAO中,这样VAO中就有了我们所需要的数据,它根据顶点配置到VBO中索取到数据,之后直接绑定相应的VAO即可,glDrawArrays()函数就是需要从VAO中拿到数据进行绘制。但是要明白的是,我们是通过VAO来间接绑定VBO的,实际上数据还是要存储在VBO中的,VAO内并没有存储顶点的数据,如果我们要绘制两个不同的三角形,我们可能需要定义两个VBO,每个VBO内有三个顶点数据。
例子:
glGenBuffers(1, &m_nQuadPositionVBO);
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadPos), fQuadPos, GL_STREAM_DRAW);
glGenBuffers(1, &m_nQuadTexcoordVBO);
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadTexcoord), fQuadTexcoord, GL_STREAM_DRAW);
glGenBuffers(1, &m_nQuadIndexVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(nQuadIndex), nQuadIndex, GL_STREAM_DRAW);
//VAO 初始化部分
glGenVertexArrays(1, &m_nQuadVAO);
glBindVertexArray(m_nQuadVAO);
//开始保存状态
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
glEnableVertexAttribArray(VAT_POSITION);
glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
glEnableVertexAttribArray(VAT_TEXCOORD);
glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
//保存结束
glBindVertexArray(NULL);
glBindBuffer(GL_ARRAY_BUFFER, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
Qt 中使用QOpenGLWidget 的VAO 例子
标准OPenGLes2.0还没有引进VAO,应该是OpenGL3.0以后才有。不过Qt封装的OPenGLes2.0可以使用VAO,它就是QOpenGLVertexArrayObject。其用法与OpenGL3.0中的VAO用法一样。接下来通过一个列子熟悉这个类的用法。
首先要调用QOpenGLVertexArrayObject::create()创建该对象。创建成功您就可以对它绑定某个VBO的状态,需要绘制时将其绑定到OPenGL上下文,最后要销毁掉。做法如下:
1.初始化:
Bind the VAO ----> Set vertex data state for this visual object ---> Unbind (release()) the VAO
2.渲染:
Bind the VAO ----> Call a glDraw*() function ----> Unbind (release()) the VAO
#ifndef OPENGL_WIDGET_H
#define OPENGL_WIDGET_H
#include <QOpenGLFunctions>
#include <QOpenGLWidget>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
class OpenGLWidget : public QOpenGLWidget,
protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit OpenGLWidget(QWidget *parent = nullptr);
~OpenGLWidget();
protected:
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
private:
void makeObject();
void makeObject_2();
void setVertexAttribute();
private:
QOpenGLShaderProgram *m_program;
QOpenGLBuffer m_vbo;
int m_matrixUniform;
QMatrix4x4 m_pMat;
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo_2;
QOpenGLVertexArrayObject m_vao_2;
};
#endif // OPENGL_WIDGET_
/
#include "opengl_widget.h"
OpenGLWidget::OpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent),
m_program(nullptr),
m_vbo(QOpenGLBuffer::VertexBuffer),
m_matrixUniform(0),
m_pMat(),
m_vao(),
m_vbo_2(QOpenGLBuffer::VertexBuffer),
m_vao_2()
{
}
OpenGLWidget::~OpenGLWidget()
{
m_vbo.destroy();
m_vbo_2.destroy();
m_vao.destroy();
m_vao_2.destroy();
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
makeCurrent();
m_vbo.create();
m_vbo_2.create();
m_program = new QOpenGLShaderProgram(this);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
":/vertex_shader.glsl");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
":/fragment_shader.glsl");
if (!m_program->link())
close();
m_matrixUniform = m_program->uniformLocation("matrix");
makeObject();
makeObject_2();
}
void OpenGLWidget::resizeGL(int w, int h)
{
float aspect = float(w)/float(h?h:1);
float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
m_pMat.setToIdentity();
m_pMat.perspective(fov, aspect, zNear, zFar);
}
void OpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
m_program->bind();
QMatrix4x4 mvMat;
mvMat.translate(0.0f, 0.0f, -3.0f);
m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
{
QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
}
{
QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
}
m_program->release();
}
void OpenGLWidget::makeObject()
{
makeCurrent();
float arrVertex[] {
// position color
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
m_vbo.bind();
m_vbo.allocate(arrVertex, sizeof(arrVertex));
int attr = -1;
attr = m_program->attributeLocation("posAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
attr = m_program->attributeLocation("colAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
m_vbo.release();
}
void OpenGLWidget::makeObject_2()
{
makeCurrent();
float arrVertex[] {
// position color
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
m_vbo_2.bind();
m_vbo_2.allocate(arrVertex, sizeof(arrVertex));
int attr = -1;
attr = m_program->attributeLocation("posAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
attr = m_program->attributeLocation("colAttr");
m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
3, sizeof(float) * 6);
m_program->enableAttributeArray(attr);
m_vbo_2.release();
}
/v s///
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
attribute vec4 posAttr;
attribute vec4 colAttr;
varying vec4 col;
uniform mat4 matrix;
void main()
{
col = colAttr;
gl_Position = matrix * posAttr;
}
/f s/
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
varying vec4 col;
void main()
{
gl_FragColor = col;
}
运行结果:
五. EBO 索引缓冲对象
这个很容易理解,就是该对象里存储的都是索引,索引的意义在于减少重复数据。
使用EBO绘图是使用GLDrawElements()函数,这个函数是要通过索引到相应的顶点缓冲区内去拿数据,如果绑定了VAO就到VAO里拿数据。那EBO要不要每次绘制的时候都重新绑定了呢,这个就要看顶点数据部分如何处理的了。如果你没有使用VAO,那么就要每次都重新绑定相应的EBO,然后先读取索引,再根据索引去到绑定的VBO里寻找数据。如果已经绑定了VAO,要注意一点:VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象,绑定VAO的同时也会自动绑定EBO。看下面这张图,索引缓冲对象会成为VAO的一个元素。
EBO也不是必须的,如果使用EBO,绘制过程将更清晰简单,EBO需配合VBO使用,索引必须指定索引的对象。
直接通过一个例子来说明ebo的用途和用法,下面的例子是绘制2个田字形的格子,分别使用线和四边形的方式绘制,考虑一 下,如果不使用ebo,设计坐标的时候就需要考虑绘制方式,用线的方式绘制时,坐标传送应该用线的方式,如例子中的田字格,使用GL_LINES方式绘制,需设计48个float型的坐标(共12条短线,每条线2个坐标,每个坐标2个float 数据),而使用GL_QUADS方式绘制,需要设计32个float型坐标,很麻烦,可以看出来,中间的很多坐标都是相同的。
这个时候使用ebo就非常简单了,先设计田字格的9个顶点,然后再设计绘制方式的索引,顶点设计和绘制方法设计完全分开,使程序更明了。
EBO与VBO使用方式类似,先使用glGenBuffers创建EBO,再使用glBindBuffer绑定EBO,然后使用glBindBuffer传送数据,注意类型为GL_ELEMENT_ARRAY_BUFFER,绘图的时候使用glDrawElements函数。
EBO示例
例子源码如下:
#include <stdlib.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GL/glut.h>
static const GLchar * vertex_source =
"#version 330 core\n"
"uniform float translate;\n"
"layout (location = 0) in vec2 position;\n"
"layout (location = 1) in vec3 color;\n"
"flat out vec3 vertex_color;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position.x+translate,position.y,0.0,1.0);\n"
"vertex_color = color;\n"
"}\0";
static const GLchar * frag_source =
"#version 330 core\n"
"flat in vec3 vertex_color;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(vertex_color,1.0);\n"
"}\n\0";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
const GLchar * shaderSource[] = {source};
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, shaderSource, 0);
glCompileShader(shader);
glAttachShader(program, shader);
}
GLint translate_loc;
GLuint vao, vbo, ebo[2];
void init()
{
GLuint program = glCreateProgram();
/* 同时加载了顶点着色器和片元着色器*/
loadShader(program, GL_VERTEX_SHADER, vertex_source);
loadShader(program, GL_FRAGMENT_SHADER, frag_source);
glLinkProgram(program);
glUseProgram(program);
translate_loc = glGetUniformLocation(program, "translate");
/* 田字格的顶点坐标*/
GLfloat vertices[][2] =
{
{ -0.9f, 0.5f}, { -0.5f, 0.5f}, { -0.1f, 0.5f}, /* 第一行*/
{ -0.9f, 0.0f}, { -0.5f, 0.0f}, { -0.1f, 0.0f}, /* 第二行*/
{ -0.9f, -0.5f}, { -0.5f, -0.5f}, { -0.1f, -0.5f}, /* 第三行*/
};
/* 每个点给一种颜色*/
GLfloat colors[][3] =
{
{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f},
{0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
};
/* 线的方式画田字格,每条线个点*/
GLint index_line[][2] =
{
{0, 1}, {1, 2}, /* 第一行线索引*/
{3, 4}, {4, 5},
{6, 7}, {7, 8},
{0, 3}, {3, 6}, /* 第一列线索引*/
{1, 4}, {4, 7},
{2, 5}, {5, 8},
};
/* 四边形的方式画田字格,每条四边形个点*/
GLint index_quad[][4] =
{
{0, 1, 4, 3}, /* 第一个格子*/
{1, 2, 5, 4},
{3, 4, 7, 6},
{4, 5, 8, 7},
};
glGenBuffers(1, &vao);
glBindBuffer(GL_ARRAY_BUFFER, vao);
/* 顶点数组和颜色数据放同一个vbo中,设置时候注意数据宽度和偏移*/
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertices)));
glEnableVertexAttribArray(1);
/* 创建画格子方式的索引,也可以用一个ebo,通过偏移量来存放索引*/
glGenBuffers(2, ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); /* 存放用线画田字格的索引*/
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_line), index_line, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]); /* 存放用线画田字格的索引*/
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_quad), index_quad, GL_STATIC_DRAW);
glLineWidth(2.0);
glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, vao);
/* 12根线,每根个索引*/
glUniform1f(translate_loc,0.0f);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glDrawElements(GL_LINES,2*12, GL_UNSIGNED_INT, (GLvoid *)(0));
/* 用偏移的方式画第二个田字格*/
glUniform1f(translate_loc,1.0f);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
glDrawElements(GL_QUADS,4*4, GL_UNSIGNED_INT, (GLvoid *)(0));
glFlush();
}
int main(int argc, char * argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowPosition(200, 200);
glutInitWindowSize(600, 400);
glutCreateWindow("ebo");
glewInit();
init();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
效果图:
六. 总结
可以说从程序传入的数据都是要放入缓冲对象的,也就是Buffer Object,像VBO和EBO,而VAO则可以看作是把这些数据进行了分类整合,根据缓冲对象里的数据进行自由配置,从而得到自己需要的数据,然后放入一个对象里。比如我从一堆顶点缓冲和索引缓冲里拿出一部分数据组成正方体,然后给它一个VAO的名字,下次用的时候直接取出来就行,我还可以拿出一些数据组成四面体,然后再给它一个名字。这样看来,VBO和EBO里的数据就好像是可以重复使用的原材料,VAO则是使用这些原材料进行加工好了的对象。
VBO:CPU把顶点数据存储到VBO,然后每次调用的时候从VBO集中发送到GPU,能够大大降低cpu到gpu的交互次数。
EBO:主要是为了减少VBO中的重复数据,但是它不能减少数据的传输,如果一个正方体有36个顶点,用索引的话就要有36个索引值,但是不同的索引可能索引到同一个顶点。
VAO:VAO则是用来保存顶点数据和配置的,没有VAO的时候,我们需要不停地把配置从CPU传到GPU,以此让GPU来知道怎么取顶点,而有了VAO(VAO是放在显存里的),我们通过绑定的VAO可以很快拿到配置,而不用从CPU程序中传入。
数据传递流程:我们定义了顶点数组以后,需要将数据载入缓存对象也就是VBO中,同时我们必须告诉OpenGL如何配置这些数据,这就需要我们绑定一个VAO,这里面存储着相应的配置,同时VAO也与相应的顶点数组做了间接绑定,所以一般来说;一个顶点数组可以对应着多个VAO,但是一个VAO一般对应一个顶点数组。
七. 参考链接:
OpenGL中VBO及VAO区别
OpenGL VAO和VBO以及EBO的使用
openGL之glsl入门5--缓冲对象vbo、vao及ebo
基于Qt的Opengles可编程管线学习——VAO(QOpenGLVertexArrayObject的使用)
基于Qt的Opengl可编程管线学习—— VBO(QOpenGLBuffer的使用)
————————————————
原文链接:https://blog.csdn.net/p942005405/article/details/103770259
标签:VAO,OPenGL,0.0,EBO,VBO,program,顶点,GL From: https://www.cnblogs.com/im18620660608/p/17210544.html