参考教程
【中文版】实例化
【英文版】Instancing
学习记录
在这篇文章中,我们来介绍一个用于绘制大量模型物体的技术:实例化(Instancing)
是否还记得我们在 高级 GLSL 这一篇文章中,我们绘制的四个正方体模型:
1 | //绘制第一个模型 |
刚开始我们需要一个一个的传入 MVP ,然后一个一个的绘制,代码冗余不说效率也不高。之后我们使用 UBO (Uniform Buffer Object)优化了代码:
1 | glBindVertexArray(vertexArrayObject); |
代码变成了这样,但我们仍然是调用多个绘制函数,如果我们的 Shader 再一样的话,那么应该是这样:
1 | glBindVertexArray(vertexArrayObject); |
亦或者使用 for 循环:
1 | glBindVertexArray(vertexArrayObject); |
这样再多的模型我们也只需要在 models 容器里添加 model 就可以了,代码的冗余似乎解决了,但是效率并没有提高。与绘制顶点本身相比,使用 glDrawArrays 或 glDrawElements 函数告诉 GPU 去绘制顶点数据会消耗更多的性能,因为 OpenGL 在绘制顶点数据之前需要做很多准备工作(比如告诉 GPU 该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些都是在相对缓慢的 CPU 到 GPU 总线(CPU to GPU Bus)上进行的)。所以,即便渲染顶点非常快,命令 GPU 去渲染却未必。
如果我们能够将数据一次性发送给 GPU ,然后使用一个绘制函数让 OpenGL 利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)。通过实例化技术,我们可以快速的绘制出大量的同类模型。
用了这么大的篇幅去介绍为什么使用实例化技术,那么这个究竟如何使用呢?
在渲染多个相同的物体时,我们需要提供他的位置(否则在一个位置渲染再多的相同物体都是没有意义的),首先我们在顶点着色器里定义位置数组。
1 | uniform vec3 positions[40]; |
我们定义了一个大小为 40 的位置索引,这时候我们需要用提供的当前渲染的物体的索引来选择相应的位置,索性 GLSL 给我们定义了这么一个内建变量:gl_InstanceID 。
Name
gl_InstanceID — contains the index of the current primitive in an instanced draw command
Description
gl_InstanceID
is a vertex language input variable that holds the integer index of the current primitive in an instanced draw command such asglDrawArraysInstanced
. If the current primitive does not originate from an instanced draw command, the value ofgl_InstanceID
is zero.
gl_InstanceID 给了我们当前所渲染的物体的 ID,从 0 开始,跟随我们渲染的物体数而自增。所以在顶点着色器代码中,使用 gl_InstanceID 索引位置数组,并加到 gl_Position 上。
1 | gl_Position = projection * view * model * vec4(aPos + positions[gl_InstanceID] , 1.0f); |
此时我们的顶点着色器应该是这个样子:
1 |
|
着色器的代码完了,现在我们需要在 OpenGL 代码中为着色器的 positions[] 变量赋值,首先我们创建一个 positions 数组:
1 | std::vector<glm::vec3> positions(0); |
之后,我们在调用 shader.use() 之后,我们为着色器变量写值:
1 | glslShader.use(); |
最后,我们调用实例化 Draw 方法:glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 40);
,请注意我们最后一个参数是要渲染的物体数量。效果如下:
如果你没有成功,请看这里:源代码