假如你有一个有许多模型的场景,而这些模型的顶点数据都一样,只是进行了不同的世界空间的变换、顶点属性等,多次绘制之后,很快将达到一个瓶颈,这是因为你
glDrawArrays或glDrawElements这样的函数(Draw call)过多。 如果我们能够将数据一次发送给GPU,然后告诉OpenGL使用一个绘制函数,将这些数据绘制为多个物体。这就是所谓的实例化(Instancing)。
引擎部分
针对同一个图元集合进行指定数量的重复绘制
void glDrawArraysInstanced(
GLenum mode,
GLint first,
GLsizei count,
GLsizei instancecount
)
void glDrawElementsInstanced(
GLenum mode,
GLsizei count,
GLenum type,
const void *indices,
GLsizei instancecount
)- 参数:
...—— 参考instancecount—— 实例数量
- 无返回值 示例:
// 法一:基于顶点绘制
glDrawArraysInstanced(GL_TRIANGLES, 0, sizeof(vertex_array), 实例数量);
// 法二:使用EBO绘制
glDrawElementsInstanced(GL_TRIANGLES, sizeof(indices_array), GL_UNSIGNED_INT, 0, 实例数量
);着色器部分
使用实例化绘制的各个实例的顶点属性一致,需要在着色器中将它们区分,并赋予不同的显示效果
gl_InstanceID
在GLSL中可以通过内置变量 gl_InstanceID 来区分不同的实例
// 方阵排列
aPos.x += float(gl_InstanceID % 10) * 0.5; // 每行10个实例,每个X轴间距0.5
aPos.y += float(gl_InstanceID / 10) * 0.5; // 每行10个实例,每行Y轴间隔0.5
// 生成颜色(RGB循环)
Color = vec3(
sin(float(gl_InstanceID) * 0.3) * 0.5 + 0.5,
cos(float(gl_InstanceID) * 0.2) * 0.5 + 0.5,
fract(float(gl_InstanceID) * 0.1)
);警告
注意
gl_InstanceID是顶点着色器的内置变量,只能在顶点着色器中访问,其他着色器需要上一阶段的着色器传递:flat out int instanceID; -> flat in int instanceID;
glVertexAttribDivisor
修改渲染过程中顶点属性的更新速率
void glVertexAttribDivisor(
GLuint index,
GLuint divisor
)- 参数:
index—— 顶点属性索引divisor—— 指定在更新顶点属性之前,将经过的实例数量
- 无返回值 示例:
// 顶点位置属性
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
......
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);在传递顶点位置属性时,未使用glVertexAttribDivisor设置更新速率,那么便意味着该属性会在每个顶点处更新一次,即顶点属性与顶点一一对应。
// 实例颜色属性
float colors[] = {
0.f, 0.f, 0.f, // 黑
1.f, 0.f, 0.f, // 红
0.f, 1.f, 0.f, // 绿
0.f, 0.f, 1.f, // 蓝
1.f, 1.f, 0.f, // 黄
0.f, 1.f, 1.f, // 青
1.f, 0.f, 1.f, // 紫
1.f, 1.f, 1.f, // 白
};
......
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glVertexAttribDivisor(1, 1); // 设置顶点属性的更新速率
......
glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 8); // 绘制8个实例
在传递实例颜色属性时,使用glVertexAttribDivisor将顶点属性点1的更新速率设置为1,那么便意味着顶点属性点1只会在每经过1个实例后更新一次,即顶点属性与实例一一对应;同理,若速率设置为2,那么顶点属性点只会在每经过2个实例后更新一次,即顶点属性与2个实例一一对应。
警告
注意
设置divisor后需要注意属性数量与实例数量的关系