实例化(高效绘制大量物体)


实例化

在之前我们绘制多个物体都是用for循环去实现,在每个循环里面单独绑定VAO,但是这样的做法会特别消耗性能。使用glDrawArrays或glDrawElements函数告诉GPU去绘制顶点数据会消耗更多的性能,因为OpenGL在绘制顶点数据之前需要做很多准备工作(比如告诉GPU该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些命令都是在相对缓慢的CPU到GPU总线上进行的。所以,即便渲染顶点非常快,命令GPU去渲染就会变得很慢。

实例化就是把数据一次性地发送给GPU,然后只使用一个绘制函数来绘制所有物体,这样可以避免CPU与GPU之间的过多通信,使用实例化渲染,只需要将glDrawArrays和glDrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced。这些渲染函数的实例化版本需要一个额外的参数:实例数量(Instance Count),它能够设置我们需要渲染的实例个数。这样我们只需要将必须的数据发送到GPU一次,然后使用一次函数调用告诉GPU它应该如何绘制这些实例。GPU将会直接渲染这些实例,而不用不断地与CPU进行通信,从而节省了很多性能。

而为了让每次绘制的物体的位置可以被我们调整,GLSL在顶点着色器中嵌入了一个内建变量:gl_InstanceID。在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。因为每个实例都有唯一的ID,我们可以建立一个数组,将ID与位置值对应起来,将每个实例放置在世界的不同位置。

为了体验这个方法,我们去渲染很多个四边形试试。

首先复制一下四边形的顶点和颜色数据:

1
2
3
4
5
6
7
8
9
10
float quadVertices[] = {
// 位置 // 颜色
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
-0.05f, -0.05f, 0.0f, 0.0f, 1.0f,

-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
0.05f, 0.05f, 0.0f, 1.0f, 1.0f
};

顶点着色器&片段着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#version 330 core 								
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;//实例化数组

out vec3 color;

void main(){
color = aColor;
gl_Position = vec4(aPos + aOffset, 0.0, 1.0);//直接使用offset属性在每次绘制的时候改变四边形位置
}

#version 330 core
out vec4 FragColor;

in vec3 fColor;

void main()
{
FragColor = vec4(fColor, 1.0);
}

首先,我们要去设置偏移量数组的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for (int y = -10; y < 10; y += 2)
{
for (int x = -10; x < 10; x += 2)
{
glm::vec2 translation;
translation.x = (float)x / 10.0f + offset;
translation.y = (float)y / 10.0f + offset;
translations[index++] = translation;
}
}

然后把偏移量存到buffer中:

1
2
3
4
GLuint instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);

最后去设置所有的VAO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GLuint quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);//后面解析quadVBO里面的数据
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);//存位置
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);//存颜色
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));

glEnableVertexAttribArray(2);//存offset
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); //接下来解析instanceVBO里面的数据
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glVertexAttribDivisor(2, 1); //告诉OpenGL该什么时候更新顶点属性的内容至新一组数据。它的第一个参数是顶点属性的位置,第二个参数是属性除数。默认情况下是0,告诉OpenGL我们需要在顶点着色器的每次迭代时更新顶点属性。将它设置为1时,是在渲染一个新实例的时候更新顶点属性。这里的意思就是位于二号位的顶点属性每画出一个实例就要更新一次。

最后画:

1
2
3
myShader->use();
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

就可以用实例化的方法画出这100个四边形:


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC