Instanced Drawing In OpenGL

Let’s say that you have at your disposal a computer model of an object, which you want to place multiple times inside a computer generated scene (for example, trees or rocks on a level map). What can you do? You could call the function that draws the object multiple times from the CPU, however that’s a pretty inefficient use of your resources. Alternatively, you could instance the object and draw it as many times as you want with one command. That’s what we’re going to talk about in this article.

So what exactly is instancing? Have you ever played an MMORPG (World of Warcraft, for example)? Have you ever noticed that they name dungeons instances? Well, that would be a good analogy. You have one object (in this case the dungeon), or a template of it at least, which is then copied multiple times (for each dungeon group an instance or copy of the dungeon is formed). However, none of these instances will be an exact copy of each other due to the groups which will go through them. They will complete them in different times, some people may die, there might me group wipes, a bug may appear, a tank may leave because his mother was calling him to dinner…

Or here’s another analogy. Let’s say you have created a computer blueprint for some object which you will physically put into production (via 3D printing or other industrial processes). That would be our basic object to be instanced, the template. Let’s also say that you don’t want all of your merchandise to be identical. And so, you will create smaller and larger instances of that product, you will color them differently, you may add to or subtract small elements from them, etc. etc. So you will end up with basically the same object, but instanced with some changes to the basic design.

In our case, you have an object model which, instead being drawn manually, command by command, will become a template for your central production computer, into which you will just feed the desired attributes for each instance of the product (in graphics, those attributes may be the position, rotation, scale, color, etc. etc.).

Before we get into the meat of the chapter, here are some appetizers in the form of a short explanation of the Vertex Shader.

THE VERTEX SHADER

This is the first one in the entire chain of shader chain, and its main purpose, as you can tell by its name, is to receive vertices and prepare them for the following shaders. The most important operation performed is determining the world position of each vertex, which is placed in gl_Position, through multiplication between the vertex’s position in object space (where the origin is the center of the object drawn) and a series of matrices, transforming it from one space to another:

Like in any shader, you can receive attributes from the previous stage and send others to the next one and access uniform values sent from the CPU, which are constant (for example, shininess coefficient, light position, etc.). With that out of the way, let’s move on.

INSTANCED DRAWING

First thing’s first, you need to create and bind your vertex array.

glGenVertexArrays(1, &gl_vertex_array_object);
glBindVertexArray(gl_vertex_array_object);

And then to place your vertices’ position inside a buffer (along with normals, indexes, etc. – each in their own separate buffer)

glGenBuffers(1,&gl_vertex_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, gl_vertex_buffer_object);
glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

You place the normals and texture coordinates in buffers in a similar way. Finally, you need to open an attribute array and a slot through which the array will be sent.

glEnableVertexAttribArray(0);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),0);

Before you draw the items in question, you might want to consider sending some attributes to each instance to differentiate them (position offset, rotation, scaling, color, texture, etc. etc.). The most important one by far is position, because without it you would just be drawing the same object in the exact same spot for how many times you instance it.

//in case you bound other array object before,
//you should bind the instanced vao again
glBindVertexArray(gl_vertex_array_object);
//generate a object
glGenBuffers(1, &posbuf);
//bind it
glBindBuffer(GL_ARRAY_BUFFER, posbuf);
//create and initialize buffer and set size
glBufferData(GL_ARRAY_BUFFER, instno*3*sizeof(GL_FLOAT), NULL, GL_STREAM_DRAW);
//introduce data inside the buffer
glBufferSubData(GL_ARRAY_BUFFER, 0, instno*3*sizeof(GL_FLOAT), pos);
//create an array of attributes
glVertexAttribPointer(3,3, GL_FLOAT, GL_FALSE,3*sizeof(GL_FLOAT),0);
//open the channel
glEnableVertexAttribArray(3);
//the rate of advance in the case of instanced drawing (for us, it’s 1)
glVertexAttribDivisor(posbuf, 1);
//do what I did above for rotation, scaling, color, texture, etc.

You can copy/paste the lines above and change them in such a way to send any attribute you want (rotation, color, etc.), changing only the buffers, the type of data and the channel.

glVertexAttribDivisor is the crux of making instancing possible, because without this one instruction, your attributes will not be passed correctly to each instance within the VS. Basically what this command does is tell the shader that this array is meant to be used in an instancing operation. The first parameter is the pointer to the array and the second one tells the VS the step to take before sending an attribute to the next instance. For example, if the value is one, then all of the attributes will be sent consecutively to their assigned instances (instance 1 will get the first attribute, instance 2 the 2nd, instance N the Nth attribute). If the value is 2, then after a sent attribute the next instance will receive the 2nd attribute after the previous one (instance 1 the 1st attribute, instance 2 the 3rd attribute, instance N the 2*N-1th attribute).

Next up, you simply draw with the following command (in my case. You can use any of the other Draw functions which have Instanced in their name):

glUseProgram(gl_program_shader_curent);
//bind the uniform buffers which hold the transformation matrices
glUniformMatrix4fv(glGetUniformLocation(gl_program_shader_curent, "model_matrix"),1,false,glm::value_ptr(model_matrix));
glUniformMatrix4fv(glGetUniformLocation(gl_program_shader_curent, "view_matrix"),1,false,glm::value_ptr(view_matrix));
glUniformMatrix4fv(glGetUniformLocation(gl_program_shader_curent, "projection_matrix"),1,false,glm::value_ptr(projection_matrix));

glDrawElementsInstanced(GL_TRIANGLES, mesh_num_indices,GL_UNSIGNED_INT,0,instno);

Moving on to the Vertex Shader bit, I should point out that transmitting position offsets, colors etc. through vertex attribute arrays isn’t the only way to insure the correct placements of your instanced objects (although it’s the most efficient). Each instance has an ID, accessible through the gl_InstanceID variable. You could conceivably code directly into your VS these values knowing the identity of each object (for example, if you want them in rows and columns, you can determine the offset by modulo and division operations and multiplying those results with the distance you want between the objects). Or you can just do a long switch-case operation if you want them in specific positions in the scene.

The code is as follows:

//you receive the attributes
layout(location = 0) in vec3 in_position;    	
layout(location = 1) in vec3 in_normal;		
layout(location = 3) in vec3 offset;
layout(location = 4) in vec3 col;

//and matrices
uniform mat4 model_matrix, view_matrix, projection_matrix;

//you send the color further on
out vec3 color;

void main(){
	
//form the transition matrix (every line is a column in this case)
	mat4 translate;
	translate[0]=vec4(1.0,0.0,0.0,0.0);
	translate[1]=vec4(0.0,1.0,0.0,0);
	translate[2]=vec4(0.0,0.0,1.0,0);
	translate[3]=vec4(offset[0],offset[1],offset[2],1.0);
	
	color=col;
	
	gl_Position = projection_matrix* view_matrix* model_matrix* translate* vec4(in_position,1); 
}

In the end, you will end up with something like this:

Now, I know what you’re thinking: Vlad, why didn’t you just sum the offset and the position, instead of creating a whole different matrix for it? Well, because in some cases you may want to rotate and scale the model, and these operations must take place before translation, which are much easier to calculate in matrix form. And plus, because it’s more elegant that way.

I must point out that without lighting, it wouldn’t look so good, and the way you can combine the color and the lighting intensity in the fragment shader is:

layout(location = 0) out vec4 out_color;

in float light;
in vec3 color;

void main(){
    out_color = vec4(light*color[0],light*color[1],light*color[2],1);
}

With the Gouraud lighting model, of course. For more on that, check out my colleague’s article.

Well, now you possess the tools. Go and create your own army of multicolored minions to serve and make you laugh.


I'm an engineer, currently employed at a financial software company. My interests include gaming, LPing and, of course, reviewing, but also game dev and graphics. Also, in the past I've dabbled in amateur photography, reviewing movies and writing short stories and blog posts. I am also a huge Song of Ice and Fire fan, but that's beside the point. Youtube Channel, Deviantart , Google + , Twitter

blog comments powered by Disqus