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 }
問題的提出
需求:我們需要同時繪制兩個立方體,以及兩個四面體
目前的解決方法
我們利用已有的知識可以這樣去完成:
- 在sendDataToOpenGL()中生成立方體形狀
- 在sendDataToOpenGL()中創建,綁定,設置立方體的VertexBuffer
- 在sendDataToOpenGL()中開啟通道0,1
- 在sendDataToOpenGL()中設置通道0,1如何獲取數據(glVertexAttribPointer)
- 在sendDataToOpenGL()中創建,綁定,設置立方體的IndexBuffer
- 在sendDataToOpenGL()中生成四面體形狀
- 在sendDataToOpenGL()中創建,綁定,設置四面體的VertexBuffer
- 在sendDataToOpenGL()中開啟通道0,1
- 在sendDataToOpenGL()中設置通道0,1如何獲取數據(glVertexAttribPointer)
- 在sendDataToOpenGL()中創建,綁定,設置四面體的IndexBuffer
- 在paintGL()中再次綁定立方體的VertexBuffer
- 在paintGL()中再次設置0,1如何獲取數據(glVertexAttribPointer)
- 在paintGL()中再次綁定立方體的IndexBuffer
- 在paintGL()中繪制立方體
- 在paintGL()中再次綁定四面體的VertexBuffer
- 在paintGL()中再次設置0,1如何獲取數據(glVertexAttribPointer)
- 在paintGL()中再次綁定四面體的IndexBuffer
- 在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(頂點數組對象)