当前位置:   article > 正文

OPenGL 学习笔记之 VBO VAO EBO 概念和使用方法总结_vao使用

vao使用

 

目录

一.  基本概念:

二. 理解缓冲对象 

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进行一次通信,十分低效。

  1. glBegin(GL_TRIANGLES);
  2. glVertex(0, 0);
  3. glVertex(1, 1);
  4. glVertex(2, 2);
  5. glEnd();

顶点数组(Vertex Array)

  顶点数组也是收集好所有的顶点,一次性发送给GPU。不过数据不是存储于GPU中的,绘制速度上没有显示列表快,优点是可以修改数据。

显示列表和顶点数组都是过时的东西了,下面的VBO和VAO才是重点!

  1. #define MEDIUM_STARS 40
  2. M3DVector2f vMediumStars[MEDIUM_STARS];
  3. //在这做点vMediumStars的设置//
  4. glVertexPointer(2, GL_FLOAT, 0, vMediumStars);
  5. 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决定的,因为同一个类型的缓冲区同时最多绑定一个目标缓冲。

下面一个简单的例子:

  1. //创建VBO及VBO赋值
  2. glGenBuffers(1, &m_nPositionVBO);
  3. glBufferData(GL_ARRAY_BUFFER,
  4. sizeof(posData), posData, GL_STREAM_DRAW);
  5. glGenBuffers(1, &m_nTexcoordVBO);
  6. glBufferData(GL_ARRAY_BUFFER,
  7. sizeof(texData), texData, GL_STREAM_DRAW);
  8. glGenBuffers(1, &m_nIndexVBO);
  9. glBufferData(GL_ELEMENT_ARRAY_BUFFER,
  10. sizeof(indexData), indexData, GL_STATIC_DRAW);
  11. //代码一,不使用shader VBO已经创建好了
  12. glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
  13. glEnableClientState(GL_VERTEX_ARRAY);
  14. glVertexPointer(2, GL_FLOAT, 0, NULL);
  15. glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
  16. glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  17. glTexCoordPointer(2, GL_FLOAT, 0, NULL);
  18. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
  19. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
  20. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
  21. glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  22. glDisableClientState(GL_VERTEX_ARRAY);
  23. glBindBuffer(GL_ARRAY_BUFFER, NULL);
  24. //代码二,使用shader
  25. glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
  26. glEnableVertexAttribArray(VAT_POSITION);
  27. glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
  28. glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
  29. glEnableVertexAttribArray(VAT_TEXCOORD);
  30. glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
  31. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
  32. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
  33. glDisableVertexAttribArray(VAT_POSITION);
  34. glDisableVertexAttribArray(VAT_TEXCOORD);
  35. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
  36. 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用完就解绑了。

  1. /头文件
  2. #ifndef OPENGL_WIDGET_H
  3. #define OPENGL_WIDGET_H
  4. #include <QOpenGLFunctions>
  5. #include <QOpenGLWidget>
  6. #include <QOpenGLShader>
  7. #include <QOpenGLShaderProgram>
  8. #include <QOpenGLBuffer>
  9. class OpenGLWidget : public QOpenGLWidget,
  10. protected QOpenGLFunctions
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit OpenGLWidget(QWidget *parent = nullptr);
  15. ~OpenGLWidget();
  16. protected:
  17. virtual void initializeGL() override;
  18. virtual void resizeGL(int w, int h) override;
  19. virtual void paintGL() override;
  20. private:
  21. void makeObject();
  22. private:
  23. QOpenGLShaderProgram *m_program;
  24. QOpenGLBuffer m_vbo;
  25. int m_matrixUniform;
  26. QMatrix4x4 m_pMat;
  27. };
  28. #endif // OPENGL_WIDGET_H
  29. 源文件
  30. #include "opengl_widget.h"
  31. OpenGLWidget::OpenGLWidget(QWidget *parent)
  32. : QOpenGLWidget(parent),
  33. m_program(nullptr),
  34. m_vbo(QOpenGLBuffer::VertexBuffer),
  35. m_matrixUniform(0),
  36. m_pMat()
  37. {
  38. }
  39. OpenGLWidget::~OpenGLWidget()
  40. {
  41. m_vbo.destroy();
  42. }
  43. void OpenGLWidget::initializeGL()
  44. {
  45. initializeOpenGLFunctions();
  46. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  47. m_vbo.create();
  48. m_program = new QOpenGLShaderProgram(this);
  49. m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
  50. ":/vertex_shader.glsl");
  51. m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
  52. ":/fragment_shader.glsl");
  53. if (!m_program->link())
  54. close();
  55. m_matrixUniform = m_program->uniformLocation("matrix");
  56. makeObject();
  57. }
  58. void OpenGLWidget::resizeGL(int w, int h)
  59. {
  60. float aspect = float(w)/float(h?h:1);
  61. float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
  62. m_pMat.setToIdentity();
  63. m_pMat.perspective(fov, aspect, zNear, zFar);
  64. }
  65. void OpenGLWidget::paintGL()
  66. {
  67. glClear(GL_COLOR_BUFFER_BIT);
  68. m_program->bind();
  69. QMatrix4x4 mvMat;
  70. mvMat.translate(0.0f, 0.0f, -3.0f);
  71. m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
  72. m_vbo.bind();
  73. glDrawArrays(GL_TRIANGLES, 0, 3);
  74. m_vbo.release();
  75. m_program->release();
  76. }
  77. void OpenGLWidget::makeObject()
  78. {
  79. float arrVertex[] = {
  80. // position color
  81. 0.0f, 0.707f, 0.0f, 1.0f, 0.0f, 0.0f,
  82. -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
  83. 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
  84. };
  85. m_vbo.bind();
  86. m_vbo.allocate(arrVertex, sizeof(arrVertex));
  87. int attr = -1;
  88. attr = m_program->attributeLocation("posAttr");
  89. m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
  90. 3, sizeof(float) * 6);
  91. m_program->enableAttributeArray(attr);
  92. attr = m_program->attributeLocation("colAttr");
  93. m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
  94. 3, sizeof(float) * 6);
  95. m_program->enableAttributeArray(attr);
  96. m_vbo.release();
  97. }

vertex 和frag

  1. /v s//
  2. #ifdef GL_ES
  3. // Set default precision to medium
  4. precision mediump int;
  5. precision mediump float;
  6. #endif
  7. attribute vec4 posAttr;
  8. attribute vec4 colAttr;
  9. varying vec4 col;
  10. uniform mat4 matrix;
  11. void main()
  12. {
  13. col = colAttr;
  14. gl_Position = matrix * posAttr;
  15. }
  16. /f s//
  17. #ifdef GL_ES
  18. // Set default precision to medium
  19. precision mediump int;
  20. precision mediump float;
  21. #endif
  22. varying vec4 col;
  23. void main()
  24. {
  25. gl_FragColor = col;
  26. }

四. 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内有三个顶点数据。

例子:

  1. glGenBuffers(1, &m_nQuadPositionVBO);
  2. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
  3. glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadPos), fQuadPos, GL_STREAM_DRAW);
  4. glGenBuffers(1, &m_nQuadTexcoordVBO);
  5. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
  6. glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadTexcoord), fQuadTexcoord, GL_STREAM_DRAW);
  7. glGenBuffers(1, &m_nQuadIndexVBO);
  8. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
  9. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(nQuadIndex), nQuadIndex, GL_STREAM_DRAW);
  10. //VAO 初始化部分
  11. glGenVertexArrays(1, &m_nQuadVAO);
  12. glBindVertexArray(m_nQuadVAO);
  13. //开始保存状态
  14. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
  15. glEnableVertexAttribArray(VAT_POSITION);
  16. glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
  17. glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
  18. glEnableVertexAttribArray(VAT_TEXCOORD);
  19. glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
  20. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
  21. //保存结束
  22. glBindVertexArray(NULL);
  23. glBindBuffer(GL_ARRAY_BUFFER, NULL);
  24. 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

  1. #ifndef OPENGL_WIDGET_H
  2. #define OPENGL_WIDGET_H
  3. #include <QOpenGLFunctions>
  4. #include <QOpenGLWidget>
  5. #include <QOpenGLShader>
  6. #include <QOpenGLShaderProgram>
  7. #include <QOpenGLBuffer>
  8. #include <QOpenGLVertexArrayObject>
  9. class OpenGLWidget : public QOpenGLWidget,
  10. protected QOpenGLFunctions
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit OpenGLWidget(QWidget *parent = nullptr);
  15. ~OpenGLWidget();
  16. protected:
  17. virtual void initializeGL() override;
  18. virtual void resizeGL(int w, int h) override;
  19. virtual void paintGL() override;
  20. private:
  21. void makeObject();
  22. void makeObject_2();
  23. void setVertexAttribute();
  24. private:
  25. QOpenGLShaderProgram *m_program;
  26. QOpenGLBuffer m_vbo;
  27. int m_matrixUniform;
  28. QMatrix4x4 m_pMat;
  29. QOpenGLVertexArrayObject m_vao;
  30. QOpenGLBuffer m_vbo_2;
  31. QOpenGLVertexArrayObject m_vao_2;
  32. };
  33. #endif // OPENGL_WIDGET_
  34. /
  35. #include "opengl_widget.h"
  36. OpenGLWidget::OpenGLWidget(QWidget *parent)
  37. : QOpenGLWidget(parent),
  38. m_program(nullptr),
  39. m_vbo(QOpenGLBuffer::VertexBuffer),
  40. m_matrixUniform(0),
  41. m_pMat(),
  42. m_vao(),
  43. m_vbo_2(QOpenGLBuffer::VertexBuffer),
  44. m_vao_2()
  45. {
  46. }
  47. OpenGLWidget::~OpenGLWidget()
  48. {
  49. m_vbo.destroy();
  50. m_vbo_2.destroy();
  51. m_vao.destroy();
  52. m_vao_2.destroy();
  53. }
  54. void OpenGLWidget::initializeGL()
  55. {
  56. initializeOpenGLFunctions();
  57. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  58. makeCurrent();
  59. m_vbo.create();
  60. m_vbo_2.create();
  61. m_program = new QOpenGLShaderProgram(this);
  62. m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
  63. ":/vertex_shader.glsl");
  64. m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
  65. ":/fragment_shader.glsl");
  66. if (!m_program->link())
  67. close();
  68. m_matrixUniform = m_program->uniformLocation("matrix");
  69. makeObject();
  70. makeObject_2();
  71. }
  72. void OpenGLWidget::resizeGL(int w, int h)
  73. {
  74. float aspect = float(w)/float(h?h:1);
  75. float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
  76. m_pMat.setToIdentity();
  77. m_pMat.perspective(fov, aspect, zNear, zFar);
  78. }
  79. void OpenGLWidget::paintGL()
  80. {
  81. glClear(GL_COLOR_BUFFER_BIT);
  82. m_program->bind();
  83. QMatrix4x4 mvMat;
  84. mvMat.translate(0.0f, 0.0f, -3.0f);
  85. m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
  86. {
  87. QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
  88. glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
  89. }
  90. {
  91. QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
  92. glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
  93. }
  94. m_program->release();
  95. }
  96. void OpenGLWidget::makeObject()
  97. {
  98. makeCurrent();
  99. float arrVertex[] {
  100. // position color
  101. 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
  102. 0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
  103. -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
  104. };
  105. QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
  106. m_vbo.bind();
  107. m_vbo.allocate(arrVertex, sizeof(arrVertex));
  108. int attr = -1;
  109. attr = m_program->attributeLocation("posAttr");
  110. m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
  111. 3, sizeof(float) * 6);
  112. m_program->enableAttributeArray(attr);
  113. attr = m_program->attributeLocation("colAttr");
  114. m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
  115. 3, sizeof(float) * 6);
  116. m_program->enableAttributeArray(attr);
  117. m_vbo.release();
  118. }
  119. void OpenGLWidget::makeObject_2()
  120. {
  121. makeCurrent();
  122. float arrVertex[] {
  123. // position color
  124. 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
  125. 0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
  126. 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
  127. };
  128. QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
  129. m_vbo_2.bind();
  130. m_vbo_2.allocate(arrVertex, sizeof(arrVertex));
  131. int attr = -1;
  132. attr = m_program->attributeLocation("posAttr");
  133. m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
  134. 3, sizeof(float) * 6);
  135. m_program->enableAttributeArray(attr);
  136. attr = m_program->attributeLocation("colAttr");
  137. m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
  138. 3, sizeof(float) * 6);
  139. m_program->enableAttributeArray(attr);
  140. m_vbo_2.release();
  141. }
  142. /v s///
  143. #ifdef GL_ES
  144. // Set default precision to medium
  145. precision mediump int;
  146. precision mediump float;
  147. #endif
  148. attribute vec4 posAttr;
  149. attribute vec4 colAttr;
  150. varying vec4 col;
  151. uniform mat4 matrix;
  152. void main()
  153. {
  154. col = colAttr;
  155. gl_Position = matrix * posAttr;
  156. }
  157. /f s/
  158. #ifdef GL_ES
  159. // Set default precision to medium
  160. precision mediump int;
  161. precision mediump float;
  162. #endif
  163. varying vec4 col;
  164. void main()
  165. {
  166. gl_FragColor = col;
  167. }

运行结果:

 

五. 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示例    

例子源码如下:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <GL/glew.h>
  4. #include <GL/glut.h>
  5. static const GLchar * vertex_source =
  6. "#version 330 core\n"
  7. "uniform float translate;\n"
  8. "layout (location = 0) in vec2 position;\n"
  9. "layout (location = 1) in vec3 color;\n"
  10. "flat out vec3 vertex_color;\n"
  11. "void main()\n"
  12. "{\n"
  13. "gl_Position = vec4(position.x+translate,position.y,0.0,1.0);\n"
  14. "vertex_color = color;\n"
  15. "}\0";
  16. static const GLchar * frag_source =
  17. "#version 330 core\n"
  18. "flat in vec3 vertex_color;\n"
  19. "out vec4 color;\n"
  20. "void main()\n"
  21. "{\n"
  22. "color = vec4(vertex_color,1.0);\n"
  23. "}\n\0";
  24. void loadShader(GLuint program, GLuint type, const GLchar * source)
  25. {
  26. const GLchar * shaderSource[] = {source};
  27. GLuint shader = glCreateShader(type);
  28. glShaderSource(shader, 1, shaderSource, 0);
  29. glCompileShader(shader);
  30. glAttachShader(program, shader);
  31. }
  32. GLint translate_loc;
  33. GLuint vao, vbo, ebo[2];
  34. void init()
  35. {
  36. GLuint program = glCreateProgram();
  37. /* 同时加载了顶点着色器和片元着色器*/
  38. loadShader(program, GL_VERTEX_SHADER, vertex_source);
  39. loadShader(program, GL_FRAGMENT_SHADER, frag_source);
  40. glLinkProgram(program);
  41. glUseProgram(program);
  42. translate_loc = glGetUniformLocation(program, "translate");
  43. /* 田字格的顶点坐标*/
  44. GLfloat vertices[][2] =
  45. {
  46. { -0.9f, 0.5f}, { -0.5f, 0.5f}, { -0.1f, 0.5f}, /* 第一行*/
  47. { -0.9f, 0.0f}, { -0.5f, 0.0f}, { -0.1f, 0.0f}, /* 第二行*/
  48. { -0.9f, -0.5f}, { -0.5f, -0.5f}, { -0.1f, -0.5f}, /* 第三行*/
  49. };
  50. /* 每个点给一种颜色*/
  51. GLfloat colors[][3] =
  52. {
  53. {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f},
  54. {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f},
  55. {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
  56. };
  57. /* 线的方式画田字格,每条线个点*/
  58. GLint index_line[][2] =
  59. {
  60. {0, 1}, {1, 2}, /* 第一行线索引*/
  61. {3, 4}, {4, 5},
  62. {6, 7}, {7, 8},
  63. {0, 3}, {3, 6}, /* 第一列线索引*/
  64. {1, 4}, {4, 7},
  65. {2, 5}, {5, 8},
  66. };
  67. /* 四边形的方式画田字格,每条四边形个点*/
  68. GLint index_quad[][4] =
  69. {
  70. {0, 1, 4, 3}, /* 第一个格子*/
  71. {1, 2, 5, 4},
  72. {3, 4, 7, 6},
  73. {4, 5, 8, 7},
  74. };
  75. glGenBuffers(1, &vao);
  76. glBindBuffer(GL_ARRAY_BUFFER, vao);
  77. /* 顶点数组和颜色数据放同一个vbo中,设置时候注意数据宽度和偏移*/
  78. glGenBuffers(1, &vbo);
  79. glBindBuffer(GL_ARRAY_BUFFER, vbo);
  80. glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
  81. glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
  82. glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
  83. glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
  84. glEnableVertexAttribArray(0);
  85. glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertices)));
  86. glEnableVertexAttribArray(1);
  87. /* 创建画格子方式的索引,也可以用一个ebo,通过偏移量来存放索引*/
  88. glGenBuffers(2, ebo);
  89. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); /* 存放用线画田字格的索引*/
  90. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_line), index_line, GL_STATIC_DRAW);
  91. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]); /* 存放用线画田字格的索引*/
  92. glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_quad), index_quad, GL_STATIC_DRAW);
  93. glLineWidth(2.0);
  94. glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
  95. glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
  96. }
  97. void display()
  98. {
  99. glClear(GL_COLOR_BUFFER_BIT);
  100. glBindBuffer(GL_ARRAY_BUFFER, vao);
  101. /* 12根线,每根个索引*/
  102. glUniform1f(translate_loc,0.0f);
  103. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
  104. glDrawElements(GL_LINES,2*12, GL_UNSIGNED_INT, (GLvoid *)(0));
  105. /* 用偏移的方式画第二个田字格*/
  106. glUniform1f(translate_loc,1.0f);
  107. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
  108. glDrawElements(GL_QUADS,4*4, GL_UNSIGNED_INT, (GLvoid *)(0));
  109. glFlush();
  110. }
  111. int main(int argc, char * argv[])
  112. {
  113. glutInit(&argc, argv);
  114. glutInitDisplayMode(GLUT_RGBA);
  115. glutInitWindowPosition(200, 200);
  116. glutInitWindowSize(600, 400);
  117. glutCreateWindow("ebo");
  118. glewInit();
  119. init();
  120. glutDisplayFunc(display);
  121. glutMainLoop();
  122. return 0;
  123. }

效果图:

 

六.  总结

       可以说从程序传入的数据都是要放入缓冲对象的,也就是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的使用)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/818291
推荐阅读
相关标签
  

闽ICP备14008679号