PDA

View Full Version : ARB_draw_instanced and ARB_instanced_arrays for former fixed-function pipeline user



elfprince13
04-01-2013, 11:55 AM
This may not be totally the right subforum since I've actually been using OpenGL for a while, but I've never really strayed away from fixed-function land before, so I suspect this may still be something of a "beginner question".

I have the following use-case in mind:

I need to render a scene with a large number of relatively low-detail objects that have the same geometry and diffuse texture
The exact selection and number will vary from frame to frame
They each have a variable tint color to be combined with the diffuse color of the texture (and possibly a limited number of other materialish properties, such as specular power or luminance)


Because the set of things to draw will be changing, but many things will have the same geometry, instancing seems like a better solution than batching.

After reading through some documentation and high level explanations of instancing, it seems like want I want to do is connected to use of glDrawElementsInstanced and glVertexAttribDivisor. However as someone coming from the fixed-function pipeline, I don't really understand how these elements tie together in shader programming land. I would greatly appreciate some pseudocode illustrating how I would go about drawing, say, 1000 cubes with a shared texture but different tinting colors and transforms.

Thanks!

malexander
04-01-2013, 02:53 PM
Those extensions are designed for the shader pipeline only. Generic vertex attributes require a shader to interpret them, and glVertexAttribDivisor() can only be applied to generic vertex attributes (not the older vertex, normal, color, etc. attributes). Using glDrawInstanced() without instanced arrays requires that the vertex shader use gl_InstanceID to differentiate instances, so both methods require shaders.

elfprince13
04-01-2013, 04:21 PM
Those extensions are designed for the shader pipeline only. Generic vertex attributes require a shader to interpret them, and glVertexAttribDivisor() can only be applied to generic vertex attributes (not the older vertex, normal, color, etc. attributes). Using glDrawInstanced() without instanced arrays requires that the vertex shader use gl_InstanceID to differentiate instances, so both methods require shaders.

malexander, thanks for the response. However, I gathered all of that from reading through the documentation. Hence my question is not "how do I use these in fixed function land", but rather, "as someone with experience in fixed function land and little experience with shaders, how do I put these pieces together to make use of the shader pipeline".

Sorry if that wasn't clear from my original post - I tried to be as clear as possible that my question was about the use of shaders and emphasizing that while I've been programming with OpenGL for some time, I've really never used shaders before and thus need some help figuring out their use for my application.

malexander
04-01-2013, 06:31 PM
Reading your post again, it's definitely my bad reading comprehension, not your clarity. My apologies.

With instancing, the vertex shader is run multiple times over the vertices of the same base geometry (say a tree model). It's up to you to vary certain elements of the model to achieve your desired effect, which is minimally the position of the model. Say all you want to do is translate your tree model. Using instanced arrays, you'd set up a vec3 vertex array which contains the various positions of the tree in addition to the vertex array containing the vertices of the tree model. Then you'd use glVertexAttribDivisor() to set the divisor to 1 for that attribute (named "instance_translate", for example). This causes that vertex input to advance only one vec3 for each instance of the entire tree drawn. Then, in your shader, you'd do something like:



#version 330
in vec3 tree_vert; // tree model vertices
in vec3 instance_translate; // instance positions

uniform mat4 modelviewproject;

void main()
{
gl_Position = modelviewproject * vec4( tree_vert + instance_translate, 1.0);
}


Any geometry or fragment shaders would remain the same as for a non-instanced draw call - you're going to be passing any instance-varying properties as outputs to the geometry or fragment shader.

Now, if you also wanted to tint the diffuse texture by some color, you'd create another VBO for the per-instance tint and also set the vertex attribute divisor to 1 for that array (if you wanted groups of 4 to share the same tint, you'd use 4 instead). Let's say this vertex array contains a vec3 color to use as the tint. The vertex shader would then look something like:



#version 330
in vec3 tree_vert;
in vec3 instance_translate; // instanced
in vec3 tint; // instanced

out vec3 diffuse_tint;

uniform mat4 modelviewproject;

void main()
{
diffuse_tint = tint;
gl_Position = modelviewproject * vec4( tree_vert + instance_translate, 1.0);
}


Now generally you have about 16 vertex attribute locations to work with, and if you wanted to do a full transform matrix (mat4), this would take up 4 vertex attribute locations. So if you want to vary a lot of material properties, you may need to start packing them up into a single VBO (such as packing diffuse tint and specular gloss into a vec4) to avoid running out of vertex attribute locations.

If you are varying your instances per frame, this might be a lot of data to send to the GPU. If your varying instances are an ever-changing subset of a set of static instances, you might want to use a different approach. You could create a single instanced integer array containing the instance numbers to render, and then put all your diffuse_tint, transform and other material properties in constant Texture Buffer Objects (TBOs) that you only fill once. This way you only need to refill and upload the index array each frame instead of multiple material properties arrays per frame. As an example:



#version 330

in vec3 tree_vert;
in int instance_index; // instanced

out vec3 diffuse_tint;

uniform samplerBuffer instance_translate;
uniform samplerBuffer tint;

uniform mat4 modelviewproject;

void main()
{
diffuse_tint = texelFetch( tint, instance_index).rgb;
gl_Position = modelviewproject * vec4( tree_vert + texelFetch( instance_translate, instance_index).xyz, 1.0);
}


You can also use the built-in GLSL vertex shader variable gl_InstanceID to index the TBOs. This is useful if you want to keep your shader GL3.2 compatible, since instanced arrays are a GL3.3 feature (OSX is currently limited to GL3.2).

As always, you need to benchmark your specific cases to figure out which approach works best for your application.

JakobProgsch
04-02-2013, 06:53 AM
Maybe this example is also useful: https://github.com/progschj/OpenGL-Examples/blob/master/06instancing1.cpp
The repository also contains examples for other ways of instancing as described by malexander.

elfprince13
04-03-2013, 06:17 PM
Thanks on both counts. I'll look through those examples, and also do some comparisons of the performance of the different methods. Interestingly, OS X, at least on Mountain Lion seems to have the extensions I mentioned, although I don't know if it has all of GL 3.3.