easy-gl › Instancing
Instancing
Draw thousands of objects in a single draw call by storing per-instance data in a buffer and using a per-instance vertex attribute divisor.
Shader
const char* vertSrc = R"(#version 330 core
layout (location = 0) in vec2 aPos; // per-vertex
layout (location = 1) in vec3 aColor; // per-vertex
layout (location = 2) in vec2 aOffset; // per-instance
layout (location = 3) in float aScale; // per-instance
out vec3 vColor;
void main() {
gl_Position = vec4(aPos * aScale + aOffset, 0.0, 1.0);
vColor = aColor;
})";
Per-instance data
struct Instance {
float offsetX, offsetY;
float scale;
};
std::vector<Instance> instances;
for (int i = 0; i < 1000; ++i) {
instances.push_back({
((float)(rand() % 2000) - 1000.0f) / 1000.0f,
((float)(rand() % 2000) - 1000.0f) / 1000.0f,
0.02f + (float)(rand() % 100) / 1000.0f
});
}
Setup VAO with two VBOs
easygl::VertexArray vao;
easygl::Buffer meshVbo, instanceVbo;
vao.create(); meshVbo.create(); instanceVbo.create();
vao.bind();
// Per-vertex attributes (attribute 0 and 1)
meshVbo.bind(easygl::BufferTarget::Array);
meshVbo.set_data(easygl::BufferTarget::Array, quadVerts, sizeof(quadVerts));
constexpr std::size_t meshStride = 5 * sizeof(float);
vao.set_attribute({0, 2, easygl::DataType::Float, false, meshStride, 0, true});
vao.set_attribute({1, 3, easygl::DataType::Float, false, meshStride, 2*sizeof(float), true});
// Per-instance attributes (attribute 2 and 3)
instanceVbo.bind(easygl::BufferTarget::Array);
instanceVbo.set_data(easygl::BufferTarget::Array,
instances.data(), instances.size() * sizeof(Instance),
easygl::BufferUsage::DynamicDraw);
constexpr std::size_t instStride = sizeof(Instance);
// offset: vec2 at byte 0
vao.set_attribute_pointer(2, 2, easygl::DataType::Float, false,
instStride, reinterpret_cast<const void*>(0));
vao.enable_attribute(2);
vao.set_attribute_divisor(2, 1); // advance once per instance
// scale: float at byte 8
vao.set_attribute_pointer(3, 1, easygl::DataType::Float, false,
instStride, reinterpret_cast<const void*>(2 * sizeof(float)));
vao.enable_attribute(3);
vao.set_attribute_divisor(3, 1);
Draw call
prog.use();
vao.bind();
device.draw_arrays_instanced(
easygl::PrimitiveType::Triangles,
0, // first vertex
meshVertexCount, // vertices per instance
1000); // instance count
Updating instances dynamically
// Move instances on CPU, then re-upload the relevant range
for (auto& inst : instances) { inst.offsetX += speed * dt; }
instanceVbo.bind(easygl::BufferTarget::Array);
instanceVbo.set_sub_data(easygl::BufferTarget::Array,
instances.data(),
instances.size() * sizeof(Instance),
0);
Key points
- Divisor 0 = advance once per vertex (default). Divisor 1 = advance once per instance.
- Use
draw_arrays_instancedordraw_elements_instanceddepending on whether you have an index buffer. - Keep the instance buffer as
DynamicDrawif you update it each frame. - For large instance counts consider GPU-side update via compute shader + SSBO.