简述
多实例渲染是一种连续执行多次相同的渲染命令的方法,并且每个渲染命令所产生的结果都会有轻微的差异。这是一种使用少量API来渲染大量几何体的有效方法。
当一个模型需要创建多个实例时,一般地可以在程序中循环调用glDrawArrays()函数完成多次绘制,这样,顶点着色器对每次输入的顶点运行一次,就会从内存中重复提取一次的数据,绘制多次相当于多次运行OPenGL整个管线。给GPU造成了潜在的处理负担。如果使用实例化方式创建,则只会多次运行几何着色器及以后的渲染管线,而不会运行全部的管线,效率会显著提升。当然,两种方法也可以同时使用。
常见的多实例绘制函数
glDrawArraysInstanced(), glDrawElementsInstanced(), glDrawElementsInstancedBaseVertex()。
voidglDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primCount) 功 能:多实例绘制函数 返回值:void 参数:前三个参数是绘制几何体图元集的参数(与glDrawArrays()中的参数完全相同 primCount参数表示绘制实例的个数) |
这个函数是glDrawArrays()的多实例版,这两个函数的参数完全是等价的,只是多了一个primCount参数,用于设置实例化的个数。其他绘制函数也有对应的实例化版本,函数glDrawElements()对应glDrawElementsInstanced(),函数glDrawElementsBaseVertex()对应glDrawElementsInstancedBaseVertex(),它们函数参数相同,只是实例化版的都会多一个参数设置实例化个数。
对于每个实例,在GLSL中的内置变量gl_InstanceID都会递增一次,新的数值会传递到顶点着色器中,以区分不同实例的顶点属性。
实例化实现思路
单纯实例化的实现非常简单,只需要调用上面实例化版本的绘制函数,并设置实例化个数就可以实现。但这样绘制的实例几乎是一摸一样的,包括位置颜色等属性,没有任何实际意义。
因此需要给每个实例都设置自己的属性,比如为每个实例设置不同的位置。
顶点着色器
1.这里的用uniform变量从程序中向实例传递数据,如果需要实例化100个,那么就在顶点着色其中声明一个数组来存储。
uniform vec2 offsets[100];
2.在原来的位置上进行偏移,根据GLSL中的内置变量gl_InstanceID索引每个实例的属性
gl_Position = vec4(aPos + offset[gl_InstanceID], 0, 1.0);
3.顶点着色器应该是这样的,其他着色器不变。
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
out vec3 fColor;
uniform vec2 offsets[100];
void main()
{
fColor = aColor;
gl_Position = vec4(aPos + offsets[gl_InstanceID], 0, 1.0);
};
应用程序
在应用程序中要做的就是准备着色器中要的数据,并传递到着色器中,每个实例需要和数据一一对应。再调用多实例绘制函数就可以完成了。
1.准备数据
要实例化100个就需要准备100分数据,也是一个数组。
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;
}
}
2.传递数据
准备好数据后需要传到着色器中,应用程序设置uniform数据要有名称索引,所以需要拼接字符串和着色器中的变量意义对应。
for (unsigned int i = 0; i < 100; i++)
{
std::stringstream ss;
std::string index;
ss << i;
index = ss.str();
shader.setVec2(("offsets[" + index + "]").c_str(), translations[i]);
}
3.多实例绘制
最后调用多实例绘制函数完成绘制
glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 100);
效果
参见:《OpenGL编程指南》第八版第3章