3D Computer Grapihcs Using OpenGL - 20 结合Buffer
在上一节的案例中,我们使用了四个Buffer Object,立方体的VertexBuffer,立方体的索引Buffer,四面体的VertexBuffer,四面体的索引Buffer。
我们这节尝试把两个图形的Vertex Buffer结合,两个图形的索引Buffer结合,形成两个Buffer,让程序更加简化。
先看最终代码: 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 VertexBufferID; 16 GLuint IndexBufferID; 17 18 GLuint cubeVertexArrayObjectID; 19 //立方体的索引数组长度 20 GLuint cubeNumIndices; 21 22 GLuint tetraVertexArrayObjectID; 23 //四面体的索引数组长度 24 GLuint tetraNumIndices; 25 26 GLuint tetraIndexByteOffset; 27 28 GLuint fullTransformUniformLocation; 29 30 31 void MyGlWindow::sendDataToOpenGL() 32 { 33 //创建Cube 34 ShapeData cube = ShapeGenerator::makeCube(); 35 //创建四面体 36 ShapeData tetra = ShapeGenerator::makeTetrahedron(); 37 38 //创建和设置VertexBuffer 39 glGenBuffers(1, &VertexBufferID); 40 glBindBuffer(GL_ARRAY_BUFFER, VertexBufferID); 41 glBufferData(GL_ARRAY_BUFFER, cube.vertexBufferSize() + tetra.vertexBufferSize(),0, GL_STATIC_DRAW); 42 glBufferSubData(GL_ARRAY_BUFFER, 0, cube.vertexBufferSize(), cube.vertices); 43 glBufferSubData(GL_ARRAY_BUFFER, cube.vertexBufferSize(), tetra.vertexBufferSize(), tetra.vertices); 44 45 //创建和设置IndexBuffer 46 glGenBuffers(1, &IndexBufferID); 47 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferID); 48 glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube.indexBufferSize() + tetra.indexBufferSize() , 0, GL_STATIC_DRAW); 49 glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, cube.indexBufferSize(), cube.indices); 50 glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, cube.indexBufferSize(), tetra.indexBufferSize(), tetra.indices); 51 52 53 cubeNumIndices = cube.numIndices; 54 tetraNumIndices = tetra.numIndices; 55 56 57 58 //设置绘制Cube的VAO 59 //生成VAO 60 glGenVertexArrays(1, &cubeVertexArrayObjectID); 61 //绑定VAO,后续的一系列状态和设置都会存储在这个VAO里。 62 glBindVertexArray(cubeVertexArrayObjectID); 63 //开启通道1(位置) 64 glEnableVertexAttribArray(0); 65 //开启通道2(颜色) 66 glEnableVertexAttribArray(1); 67 //绑定顶点数据ID到绑定点 68 glBindBuffer(GL_ARRAY_BUFFER, VertexBufferID); 69 //设置通道1如何获取数据 70 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0); 71 //设置通道2如何获取数据 72 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (void*)(sizeof(float) * 3)); 73 //绑定索引数据ID到绑定点 74 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferID); 75 76 77 //设置绘制四面体的VAO 78 glGenVertexArrays(1, &tetraVertexArrayObjectID); 79 glBindVertexArray(tetraVertexArrayObjectID); 80 //开启通道1(位置) 81 glEnableVertexAttribArray(0); 82 //开启通道2(颜色) 83 glEnableVertexAttribArray(1); 84 //绑定顶点数据ID到绑定点 85 glBindBuffer(GL_ARRAY_BUFFER, VertexBufferID); 86 //设置通道1如何获取数据 87 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (void*)(cube.vertexBufferSize())); 88 //设置通道2如何获取数据 89 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (void*)(cube.vertexBufferSize() +sizeof(float) * 3)); 90 //绑定索引数据ID到绑定点 91 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferID); 92 93 tetraIndexByteOffset = cube.indexBufferSize(); 94 95 cube.cleanUp(); 96 tetra.cleanUp(); 97 } 98 99 void MyGlWindow::installShaders() 100 { 101 //... 102 } 103 104 void MyGlWindow::initializeGL() 105 { 106 glewInit(); 107 glEnable(GL_DEPTH_TEST); 108 sendDataToOpenGL(); 109 installShaders(); 110 } 111 112 void MyGlWindow::paintGL() 113 { 114 glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); 115 glViewport(0, 0, width(), height()); 116 117 //绑定cube的VAO,下面绘制的都是立方体-------------------------------------- 118 glBindVertexArray(cubeVertexArrayObjectID); 119 120 glm::mat4 fullTransformMatrix; 121 glm::mat4 viewToProjectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f); 122 glm::mat4 worldToViewMatrix = camera.getWorldToViewMatrix(); 123 glm::mat4 worldToProjectionMatrix = viewToProjectionMatrix * worldToViewMatrix; 124 125 //绘制Cube1 126 glm::mat4 cube1ModelToWorldMatrix = 127 glm::translate(glm::vec3(-1.0f, 0.0f, -3.0f))* 128 glm::rotate(36.0f, glm::vec3(1.0f, 0.0f, 0.0f)); 129 130 fullTransformMatrix = worldToProjectionMatrix * cube1ModelToWorldMatrix; 131 glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]); 132 glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0); 133 134 //绘制Cube2 135 glm::mat4 cube2ModelToWorldMatrix = 136 glm::translate(glm::vec3(1.0f, 0.0f, -3.75f))* 137 glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f)); 138 fullTransformMatrix = worldToProjectionMatrix * cube2ModelToWorldMatrix; 139 glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]); 140 glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0); 141 142 //绑定Tetra的VAO,下面绘制的都是四面体-------------------------------------- 143 glBindVertexArray(tetraVertexArrayObjectID); 144 145 //绘制Tetra1 146 glm::mat4 tetra1ModelToWorldMatrix = 147 glm::translate(glm::vec3(1.0f, -2.0f, -3.75f))* 148 glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f)); 149 fullTransformMatrix = worldToProjectionMatrix * tetra1ModelToWorldMatrix; 150 glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]); 151 glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, (void*)tetraIndexByteOffset); 152 153 glm::mat4 tetra2ModelToWorldMatrix = 154 glm::translate(glm::vec3(-3.0f, -2.0f, -3.75f))* 155 glm::rotate(36.0f, glm::vec3(1.0f, 1.0f, 0.0f)); 156 fullTransformMatrix = worldToProjectionMatrix * tetra2ModelToWorldMatrix; 157 glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]); 158 glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, (void*)tetraIndexByteOffset); 159 } 160 161 162 std::string MyGlWindow::ReadShaderCode(const char* fileName) 163 { 164 //... 165 } 166 167 void MyGlWindow::mouseMoveEvent(QMouseEvent * e) 168 { 169 //... 170 } 171 172 void MyGlWindow::keyPressEvent(QKeyEvent * e) 173 { 174 //... 175 }
注意:为了方便起见,我们把原先setupVertexArray函数的内容全部移动到sendDataToOpenGL函数中了。
下面详解改动的过程:
- 首先合并了两个顶点数组和两个索引数组, cubeVertexBufferID + tetraVertexBufferID -> VertexBufferID, cubeIndexBufferID + tetraIndexBufferID -> IndexBufferID。
- 在sendDataToOpenGL()函数的一开始,我们先定义好两个图形。
- 合并了顶点数组和索引数组以后,在初始化时候就要初始化足够的长度,在41行我们指定VertexBuffer的长度为: cube.vertexBufferSize() + tetra.vertexBufferSize(),正好容纳两个顶点数组,而数组内容暂时为空(第三个参数为0)。
- 然后使用两个glBufferSubData函数分块给顶点数组填充内容,42,43行分别给VertexBuffer的第一部分和第二部分填充了数据,注意第二个和第三个参数,分别指定了开始的偏移值和数组的长度。这也是为什么42行的第二个参数为0,而43行的第二个参数为cube.vertexBuffer()的原因。
- 顶点数组设定好以后,下面就设定索引数组(46-50),其设定方法和顶点数组类似,就不赘述了。
- 我们把sendDataToOpenGL函数和setupVertexArray函数合并,并且把cube.cleanUp()和tetra.cleanUp()放在了最后,原因是后面还需要使用到cube对象的相关属性
- 58-91行中只改动了87和89行的最后一个参数。这里的改动主要是针对VertexBuffer的,因为VertexBuffer发生了变化,所以四面体的位置和颜色信息在数组中都要往后整体偏移cube.vertexBufferSize()个位置。
- 93行我们定义了一个tetraIndexByteOffset成员,用来记录立方体的索引Buffer的长度(绘制的时候要用)
- 在绘制函数中,内容同样基本没变,只是改变了151行和158行的最后一个参数,这里是针对IndexBuffer的,因为glDrawElements()函数的最后一个参数是说明索引Buffer数组的偏移值的(顶点Buffer在前面已经用glVertexAttribPointer函数设置过了),因此,在绘制四面体时候需要向后偏移上一步我们记录的tetraIndexByteOffset个单位。绘制立方体则不需要偏移,因为它们仍处在数组的开头。
修改完成后,运行,结果和上节一样,但是逻辑上我们已经把四个数组简化成2个数组了。
PS:我们在这里需要对之前的代码加个“补丁”,我们之前创建的Buffers都没有删除,这会造成内存泄露,所以我们给MyGlWindow类加一个析构函数.
1 MyGlWindow::~MyGlWindow() 2 { 3 glDeleteBuffers(1, &VertexBufferID); 4 glDeleteBuffers(1, &IndexBufferID); 5 glUseProgram(0); 6 glDeleteProgram(programID); 7 }
把之前的VertexBuffer,IndexBuffer都删除掉。另外也要删除掉Program,但是主要在删除Program 之前需要glUseProgram(0)。