3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)

Posted 游戏开发技术笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)相关的知识,希望对你有一定的参考价值。

大部分OpenGL教程都会在一开始就讲解VAO,但是该教程的作者认为这是很不合理的,因为要理解它的作用需要建立在我们此前学过的知识基础上。因此直到教程已经进行了一大半,作者才引入VAO这个概念。在我看来这也是非常合理和自然的。

先预览一下最终的代码逻辑:

准备工作

为了讲解后面的内容,我们对代码进行了更改(算是回退吧,改回到以前不使用Instancing的版本):

  • 去掉了sendDataToOpenGL()函数中关于实例化的部分代码
  • 把VertexShader中的MVP矩阵改回Uniform
  • 在paintGL()函数中直接提供MVP矩阵的信息,并改回使用glDrawElements函数绘制

更改以后的VetexShader代码:

 1 #version 430                           
 2                                        
 3 in layout(location=0) vec3 position;   
 4 in layout(location=1) vec3 color;
 5 
 6 uniform mat4 fullTransformMatrix;
 7 out vec3 passingColor;
 8                                        
 9 void main()                            
10 {  
11   gl_Position = fullTransformMatrix * vec4(position,1);
12   passingColor= color;           
13 }

MyGlWindow.cpp文件的更改请参考在应用实例化绘制之前的代码,这里就不再重复了。

 

另外我们在ShapeGenerator中添加了一种新的图形,四面体。

代码如下:

 1 ShapeData ShapeGenerator::makeTetrahedron()
 2 {
 3     ShapeData ret;
 4     Vertex stackVerts[] =
 5     {
 6         glm::vec3(-0.289f, -0.408f, -0.500f), //0
 7         glm::vec3(+1.0f, 0.0f, 0.0f),   //Color
 8         glm::vec3(-0.289f, -0.408f, 0.500f), //1
 9         glm::vec3(0.0f, +1.0f, 0.0f),    //Color
10         glm::vec3(0.577f, -0.408f, 0.000f), //2
11         glm::vec3(0.0f, 0.0f, +1.0f), //Color
12         glm::vec3(0.000f, 0.408f, 0.000f), //3
13         glm::vec3(+0.0f, +1.0f, +1.0f), //Color        
14     };
15 
16     ret.numVertices = NUM_ARRAY_ELEMENTS(stackVerts);
17     ret.vertices = new Vertex[ret.numVertices];
18     memcpy(ret.vertices, stackVerts, sizeof(stackVerts));
19 
20     unsigned short stackIndices[] =
21     {
22         0,1,2,
23         0,1,3,
24         1,2,3,
25         2,0,3,
26     };
27 
28     ret.numIndices = NUM_ARRAY_ELEMENTS(stackIndices);
29     ret.indices = new GLushort[ret.numIndices];
30     memcpy(ret.indices, stackIndices, sizeof(stackIndices));
31     return ret;
32 }

 

问题的提出

需求:我们需要同时绘制两个立方体,以及两个四面体

目前的解决方法

我们利用已有的知识可以这样去完成:

  1. 在sendDataToOpenGL()中生成立方体形状
  2. 在sendDataToOpenGL()中创建,绑定,设置立方体的VertexBuffer
  3. 在sendDataToOpenGL()中开启通道0,1
  4. 在sendDataToOpenGL()中设置通道0,1如何获取数据(glVertexAttribPointer)
  5. 在sendDataToOpenGL()中创建,绑定,设置立方体的IndexBuffer
  6. 在sendDataToOpenGL()中生成四面体形状
  7. 在sendDataToOpenGL()中创建,绑定,设置四面体的VertexBuffer
  8. 在sendDataToOpenGL()中开启通道0,1
  9. 在sendDataToOpenGL()中设置通道0,1如何获取数据(glVertexAttribPointer)
  10. 在sendDataToOpenGL()中创建,绑定,设置四面体的IndexBuffer
  11. 在paintGL()中再次绑定立方体的VertexBuffer
  12. 在paintGL()中再次设置0,1如何获取数据(glVertexAttribPointer)
  13. 在paintGL()中再次绑定立方体的IndexBuffer
  14. 在paintGL()中绘制立方体
  15. 在paintGL()中再次绑定四面体的VertexBuffer
  16. 在paintGL()中再次设置0,1如何获取数据(glVertexAttribPointer)
  17. 在paintGL()中再次绑定四面体的IndexBuffer
  18. 在paintGL()中绘制四面体

我们可以看到,在每次绘制中都要进行一起“切换”:

  • 先切换到立方体的状态,绑定VertexBuffer,设置数据格式,绑定IndexBuffer,绘制立方体
  • 再切换到四面体的状态,绑定VertexBuffer,设置数据格式,绑定IndexBuffer,绘制四面体

这个过程是必须的,如果不进行“切换”,会绘制错误的数据。

引入VAO的解决方案

VAO是顶点数组对象的简称,可以理解为一种“容器”,包含了绘制某种图形所需要的所有状态。

我们先给MyGlWindow类增加一个成员函数setupVertexArrays(),在initializeGL()函数中的sendDataToOpenGL()函数后面调用它。

1 void MyGlWindow::initializeGL()
2 {
3     glewInit();
4     glEnable(GL_DEPTH_TEST);
5     sendDataToOpenGL();
6     setupVertexArrays();
7     installShaders();
8 }

在setupVertexArrays()函数中,我们分别为立方体和四面体创建了两个VAO,并分别把相关的设置都在VAO后准备好。

然后在paintGL()绘制时,只需要先绑定立方体的VAO,然后进行绘制,就会绘制立方体,再绑定四面体的VAO,然后进行绘制,就会绘制四面体。

看看完整代码:

MyGlWindow.h:

 1 #pragma once
 2 #include <QtOpenGL\\qgl.h>
 3 #include <string>
 4 #include "Camera.h"
 5 
 6 class MyGlWindow :public QGLWidget
 7 {
 8 protected:
 9     void sendDataToOpenGL();
10     void installShaders();
11     void initializeGL();
12     void paintGL();
13     GLuint transformMatrixBufferID;
14     Camera camera;
15     std::string ReadShaderCode(const char* fileName);
16     void mouseMoveEvent(QMouseEvent*);
17     void keyPressEvent(QKeyEvent*);
18     void setupVertexArrays();
19 };

 

MyGlWindow.cpp:

  1 #include <gl\\glew.h>
  2 #include "MyGlWindow.h"
  3 #include <iostream>
  4 #include <fstream>
  5 #include <glm\\gtc\\matrix_transform.hpp>
  6 #include <glm\\gtx\\transform.hpp>
  7 #include <ShapeGenerator.h>
  8 #include <Qt3DInput\\qmouseevent.h>
  9 #include <Qt3DInput\\qkeyevent.h>
 10 
 11 
 12 GLuint programID;
 13 
 14 //立方体的索引数组长度
 15 GLuint cubeNumIndices;
 16 //立方体的VAO ID
 17 GLuint cubeVertexArrayObjectID;
 18 //立方体的VertexBufferID
 19 GLuint cubeVertexBufferID;
 20 //立方体的IndexBuffer的ID
 21 GLuint cubeIndexBufferID;
 22 
 23 
 24 
 25 //四面体的索引数组长度
 26 GLuint tetraNumIndices;
 27 //四面体的VAO ID
 28 GLuint tetraVertexArrayObjectID;
 29 //四面体的BufferID
 30 GLuint tetraVertexBufferID;
 31 //四面体的IndexBufferID
 32 GLuint tetraIndexBufferID;
 33 
 34 
 35 
 36 GLuint fullTransformUniformLocation;
 37 
 38 void MyGlWindow::sendDataToOpenGL()
 39 {
 40     //创建Cube
 41     ShapeData cube = ShapeGenerator::makeCube();
 42 
 43     //创建和设置VertexBuffer
 44     glGenBuffers(1, &cubeVertexBufferID);
 45     glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
 46     glBufferData(GL_ARRAY_BUFFER, cube.vertexBufferSize(), cube.vertices, GL_STATIC_DRAW);
 47 
 48     //创建和设置IndexBuffer
 49     glGenBuffers(1, &cubeIndexBufferID);
 50     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID);
 51     glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube.indexBufferSize(), cube.indices, GL_STATIC_DRAW);
 52 
 53     cubeNumIndices = cube.numIndices;
 54     cube.cleanUp();
 55 
 56     //创建四面体
 57     ShapeData tetra = ShapeGenerator::makeTetrahedron();
 58 
 59     //创建和设置VertexBuffer
 60     glGenBuffers(1, &tetraVertexBufferID);
 61     glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
 62     glBufferData(GL_ARRAY_BUFFER, tetra.vertexBufferSize(), tetra.vertices, GL_STATIC_DRAW);
 63 
 64     //创建和设置IndexBuffer
 65     glGenBuffers(1, &tetraIndexBufferID);
 66     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID);
 67     glBufferData(GL_ELEMENT_ARRAY_BUFFER, tetra.indexBufferSize(), tetra.indices, GL_STATIC_DRAW);
 68 
 69     tetraNumIndices = tetra.numIndices;
 70     tetra.cleanUp();
 71 
 72 }
 73 
 74 void MyGlWindow::setupVertexArrays()
 75 {
 76     //设置绘制Cube的VAO
 77     //生成VAO
 78     glGenVertexArrays(1, &cubeVertexArrayObjectID);
 79     //绑定VAO,后续的一系列状态和设置都会存储在这个VAO里。
 80     glBindVertexArray(cubeVertexArrayObjectID);
 81 
 82     //开启通道1(位置)
 83     glEnableVertexAttribArray(0);
 84     //开启通道2(颜色)
 85     glEnableVertexAttribArray(1);
 86 
 87     //绑定顶点数据ID到绑定点
 88     glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
 89     //设置通道1如何获取数据
 90     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0);
 91     //设置通道2如何获取数据
 92     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(float) * 3));
 93 
 94     //绑定索引数据ID到绑定点
 95     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID);
 96 
 97 
 98     //设置绘制四面体的VAO
 99     glGenVertexArrays(1, &tetraVertexArrayObjectID);
100     glBindVertexArray(tetraVertexArrayObjectID);
101 
102     //开启通道1(位置)
103     glEnableVertexAttribArray(0);
104     //开启通道2(颜色)
105     glEnableVertexAttribArray(1);
106 
107     //绑定顶点数据ID到绑定点
108     glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
109     //设置通道1如何获取数据
110     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0);
111     //设置通道2如何获取数据
112     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(float) * 3));
113 
114     //绑定索引数据ID到绑定点
115     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID);
116 
117 
118 }
119 
120 void MyGlWindow::installShaders()
121 {
122     GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
123     GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
124 
125     std::string tmp = ReadShaderCode("VertexShaderCode.glsl");
126     const char* vertexShaderCode = tmp.c_str();
127     glShaderSource(vertexShaderID, 1, &vertexShaderCode, 0);
128 
129     tmp = ReadShaderCode("FragmentShaderCode.glsl");
130     const char* fragmentShaderCode = tmp.c_str();    
131     glShaderSource(fragmentShaderID, 1, &fragmentShaderCode, 0);
132 
133     glCompileShader(vertexShaderID);
134     glCompileShader(fragmentShaderID);
135 
136     programID = glCreateProgram();
137     glAttachShader(programID, vertexShaderID);
138     glAttachShader(programID, fragmentShaderID);
139 
140     glLinkProgram(programID);
141 
142     glUseProgram(programID);
143 }
144 
145 void MyGlWindow::initializeGL()
146 {
147     glewInit();
148     glEnable(GL_DEPTH_TEST);
149     sendDataToOpenGL();
150     setupVertexArrays();
151     installShaders();
152 }
153 
154 void MyGlWindow::paintGL()
155 {
156     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
157     glViewport(0, 0, width(), height());
158 
159     //绑定cube的VAO,下面绘制的都是立方体--------------------------------------
160     glBindVertexArray(cubeVertexArrayObjectID);
161 
162     glm::mat4 fullTransformMatrix;
163     glm::mat4 viewToProjectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f);
164     glm::mat4 worldToViewMatrix = camera.getWorldToViewMatrix();
165     glm::mat4 worldToProjectionMatrix = viewToProjectionMatrix * worldToViewMatrix;
166 
167     //绘制Cube1
168     glm::mat4 cube1ModelToWorldMatrix =
169         glm::translate(glm::vec3(-1.0f, 0.0f, -3.0f))*
170         glm::rotate(36.0f, glm::vec3(1.0f, 0.0f, 0.0f));
171 
172     fullTransformMatrix = worldToProjectionMatrix * cube1ModelToWorldMatrix;
173     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
174     glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0);
175 
176     //绘制Cube2
177     glm::mat4 cube2ModelToWorldMatrix = 
178         glm::translate(glm::vec3(1.0f, 0.0f, -3.75f))*
179         glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
180     fullTransformMatrix = worldToProjectionMatrix * cube2ModelToWorldMatrix;
181     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
182     glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0);
183 
184     //绑定Tetra的VAO,下面绘制的都是四面体--------------------------------------
185     glBindVertexArray(tetraVertexArrayObjectID);
186 
187     //绘制Tetra1
188     glm::mat4 tetra1ModelToWorldMatrix =
189         glm::translate(glm::vec3(1.0f, -2.0f, -3.75f))*
190         glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
191     fullTransformMatrix = worldToProjectionMatrix * tetra1ModelToWorldMatrix;
192     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
193     glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, 0);
194 
195     glm::mat4 tetra2ModelToWorldMatrix =
196         glm::translate(glm::vec3(-3.0f, -2.0f, -3.75f))*
197         glm::rotate(36.0f, glm::vec3(1.0f, 1.0f, 0.0f));
198     fullTransformMatrix = worldToProjectionMatrix * tetra2ModelToWorldMatrix;
199     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
200     glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, 0);
201 }
202 
203 
204 std::string MyGlWindow::ReadShaderCode(const char* fileName)
205 {
206     std::ifstream myInput(fileName);
207     if (!myInput.good())
208     {
209         std::cout << "File failed to load..." << fileName;
210         exit(1);
211     }
212     return std::string(
213         std::istreambuf_iterator<char>(myInput),
214         std::istreambuf_iterator<char>());
215 }
216 
217 void MyGlWindow::mouseMoveEvent(QMouseEvent * e)
218 {
219     camera.mouseUpdate(glm::vec2(e->x(), e->y()));
220     repaint();
221 }
222 
223 void MyGlWindow::keyPressEvent(QKeyEvent * e)
224 {
225     switch (e->key())
226     {
227     case Qt::Key::Key_W:
228         camera.moveForward();
229         break;
230     case Qt::Key::Key_S:
231         camera.moveBackward();
232         break;
233     case Qt::Key::Key_A:
234         camera.strafeLeft();
235         break;
236     case Qt::Key::Key_D:
237         camera.strafeRight();
238         break;
239     case Qt::Key::Key_Q:
240         camera.moveUp();
241         break;
242     case Qt::Key::Key_E:
243         camera.moveDown();
244         break;
245 
246     default:
247         break;
248     }
249     repaint();
250 }

总结一下加入VAO以后的绘制流程

1. 初始化数据sendDataToOpenGL():

  • 创建/绑定/设置内容  立方体的VertexBuffer
  • 创建/绑定/设置内容  立方体的IndexBuffer
  • 为四面体重复上述步骤

2. 设置VAO, setupVertexArrays():

  • 创建/绑定立方体的VAO
  • 开启通道0,1
  • 绑定VertexBuffer
  • 设置通道0,1的获取数据格式
  • 绑定IndexBuffer
  • 为四面体重复上述步骤

3. 绘制,paintGL()

  • 绑定立方体的VAO
  • 绘制两个立方体
  • 绑定四面体的VAO
  • 绘制两个四面体

从代码中我们可以发现OpenGL中“绑定”这一操作的模式:“绑定"以后相当于进入了一种环境,之后我们可以对其进行设置,再次“绑定”相当于又重新进入了之前设置好的环境。这一种模式对VAO, VBO都是适用的。

最终效果:

 

代码下载: https://mrart.coding.net/p/3DGraphics/d/3DGraphics/git/releases, 找到VAO这个Release

以上是关于3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)的主要内容,如果未能解决你的问题,请参考以下文章

3D Computer Grapihcs Using OpenGL - 13 优化矩阵

3D Computer Grapihcs Using OpenGL - 20 结合Buffer

3D Computer Grapihcs Using OpenGL - 04 First Triangle

3D Computer Grapihcs Using OpenGL - 10 Color Buffer

3D Computer Grapihcs Using OpenGL - 14 OpenGL Instancing

3D Computer Grapihcs Using OpenGL - 08 Text File Shaders