1. 程式人生 > >3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(頂點數組對象)

3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(頂點數組對象)

mvp moved body 體重 transform mode 創建 unsigned ray

大部分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都是適用的。

最終效果:

技術分享圖片

3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(頂點數組對象)