Instance Rendering
Imagine we need to render hundreds of entities with the same model but located in different position and many even with different scales. Something like a forest or a ring of asteroids. Making all those individual draw calls turns out to be very inefficient.
Luckily, OpenGL provides a way to draw multiple elements of the same geometry (instances) in one pass.
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, numEntities);
The trick now is, how do we communicate to the shader the position of all those entities in one go.
We can use a simple uniform array and use the gl_InstanceID that OpenGL provides for us:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
out vec3 fColor;
uniform vec2 offsets[100];
void main()
{
vec2 offset = offsets[gl_InstanceID];
gl_Position = vec4(aPos + offset, 0.0, 1.0);
fColor = aColor;
}
We can then set that uniform:
shader.use();
for(unsigned int i = 0; i < 100; i++)
{
shader.setVec2(("offsets[" + std::to_string(i) + "]")), translations[i]);
}
This works just fine but if we want to draw even more entities, soon we will reach a limit of data that can be sent through uniform data.
Another option is to use an instanced array. These are used as vertex attributes that are updated per instance and not per vertex.
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;
out vec3 fColor;
void main()
{
gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
fColor = aColor;
}
For example, with this simple shader, we want to make the attribute aOffset change per instance.
For this we first need to create a vertex buffer object and fill it with the offsets.
unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
Then we need to bind it to the attribute in location #2. The key here is to set glVertexAttribDivisor(2, 1). This tells
OpenGL that this attribute changes per instance and not per vertex.
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// This is the key
glVertexAttribDivisor(2, 1);