假如你有一个有许多模型的场景,而这些模型的顶点数据都一样,只是进行了不同的世界空间的变换、顶点属性等,多次绘制之后,很快将达到一个瓶颈,这是因为你glDrawArraysglDrawElements这样的函数(Draw call)过多。 如果我们能够将数据一次发送给GPU,然后告诉OpenGL使用一个绘制函数,将这些数据绘制为多个物体。这就是所谓的实例化(Instancing)

引擎部分

针对同一个图元集合进行指定数量的重复绘制

C
void glDrawArraysInstanced(
    GLenum mode,
    GLint first,
    GLsizei count,
    GLsizei instancecount
)
void glDrawElementsInstanced(
    GLenum mode,
    GLsizei count,
    GLenum type,
    const void *indices,
    GLsizei instancecount
)
点击展开查看更多
C
// 法一:基于顶点绘制
glDrawArraysInstanced(GL_TRIANGLES, 0, sizeof(vertex_array), 实例数量);
// 法二:使用EBO绘制
glDrawElementsInstanced(GL_TRIANGLES, sizeof(indices_array), GL_UNSIGNED_INT, 0, 实例数量
);
点击展开查看更多

着色器部分

使用实例化绘制的各个实例的顶点属性一致,需要在着色器中将它们区分,并赋予不同的显示效果

gl_InstanceID

在GLSL中可以通过内置变量 gl_InstanceID 来区分不同的实例

GLSL
// 方阵排列
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)
);
点击展开查看更多

glVertexAttribDivisor

修改渲染过程中顶点属性的更新速率

C
void glVertexAttribDivisor(
	GLuint index,
 	GLuint divisor
)
点击展开查看更多
C
// 顶点位置属性
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设置更新速率,那么便意味着该属性会在每个顶点处更新一次,即顶点属性与顶点一一对应。

C
// 实例颜色属性
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个实例一一对应。

版权声明

作者: Chaim

链接: https://chaim.eu.org/posts/%E5%AE%9E%E4%BE%8B%E5%8C%96-instanced/

许可证: CC BY-NC-SA 4.0

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Please attribute the source, use non-commercially, and maintain the same license.

开始搜索

输入关键词搜索文章内容

↑↓
ESC
⌘K 快捷键