实例化渲染适用于使用同一个模型渲染多次的情景,比如草地,一堆岩石等。
1、先看一个渲染方形100次的例子:
其顶点着色器会有少许适应实例化渲染的变化
#version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec3 aColor; out vec3 fColor; uniform vec2 offsets[100]; void main() { vec2 offset = offsets[gl_InstanceID]; gl_Position = vec4(aPos + offset, 0.0, 1.0); fColor = aColor; }
可以看到着色器中传入了100个偏移向量。顶点着色器中内置了一个变量gl_InstanceID,此变量表示当前绘制的实例编号(从0开始)。那么我们可以用此内置变量索引对应的偏移向量,用来在屏幕不同的地方绘制方形。
向顶点着色器中传入100个偏移向量的代码如下:
shader.use(); for(unsigned int i = 0; i < 100; i++) { stringstream ss; string index; ss << i; index = ss.str(); shader.setVec2(("offsets[" + index + "]").c_str(), translations[i]); }
渲染代码如下:
void Quad::Draw_Instancing() { glBindVertexArray(VAO); glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100); glBindVertexArray(0); }
2、使用实例化数组
向着色器中传入uniform数据是有最大数量限制,我们可以使用实例化数组传入数据,可以解决数量限制问题,当然还是会限制于内存大小的,只要内存允许就可以极大数量的传入数据。
顶点着色器如下:
#version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aOffset; out vec3 fColor; void main() { vec2 pos=aPos*(gl_InstanceID/100.0); gl_Position = vec4(pos+aOffset, 0.0, 1.0); fColor=aColor; }
现在去掉了uniform 偏移向量,改为由顶点属性传入了
layout (location = 2) in vec2 aOffset;
那么同其它顶点属性一样得将偏移向量存入缓存
glGenBuffers(1, &this->instanceVBO); glBindBuffer(GL_ARRAY_BUFFER, this->instanceVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &this->translations[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0);
设置顶点属性指针
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, this->instanceVBO); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); glBindBuffer(GL_ARRAY_BUFFER, 0); glVertexAttribDivisor(2, 1); glBindVertexArray(0);
可以发现现在比以往多加了一行代码
glVertexAttribDivisor(2, 1);
其中第一个参数指顶点属性2,第二个参数可以取0,1,2……。当取值0表示逐顶点更新,也就是默认的方式;取值1时表示逐实例更新;其它值x,表示每x个实例更新一次。
3、看一个复杂点的例子,绘制行星带
其中包含一个大的行星,然后一个环绕它的岩石带。这些岩石是一样的,我们当然可以将一个岩石绘制多次,每个岩石只有model矩阵不同,这样的话在我的机器上可以正常绘制10000个岩石,帧率还能在36帧。
使用实例化渲染后可以正常渲染1000000个岩石,帧率在20帧,可以看出实例化渲染可以比正常渲染快100倍,当然这是就这个例子而言。
实例化渲染需要在顶点着色器中的顶点属性中传入model矩阵
layout (location = 3) in mat4 model;
传入着色器的代码如下:
void PlanetaryBelt::setupBuffer(Model* obj) { glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, this->amount * sizeof(glm::mat4), &this->modelMatrices[0], GL_STATIC_DRAW); std::vector<AssimpMesh>* meshes = obj->assimpModel->GetAssimpMesh(); for (unsigned int i = 0; i < meshes->size(); i++) { glBindVertexArray(meshes->at(i).GetVAOBuffer()); GLsizei vec4Size = sizeof(glm::vec4); glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0); glEnableVertexAttribArray(4); glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size)); glEnableVertexAttribArray(5); glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size)); glEnableVertexAttribArray(6); glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size)); glVertexAttribDivisor(3, 1); glVertexAttribDivisor(4, 1); glVertexAttribDivisor(5, 1); glVertexAttribDivisor(6, 1); glBindVertexArray(0); } }
可以看到代码较之前略有变化,因为顶点属性允许的最大数据大小为vec4,所以我们要在顶点属性位置3,4,5,6分别传入矩阵的一列值,然后在着色器中获取一个mat4。