MULTIMEDIA

Open GL : Drawing a lot of Geometry Efficiently (part 3) - Getting Your Data Automatically

9/9/2012 9:50:24 PM

Getting Your Data Automatically

When you call glDrawArraysInstanced or glDrawElementsInstanced, the built-in variable gl_InstanceID will be available in your shaders to tell you which instance you’re working on, and it will increment by one for each new instance of the geometry that you’re rendering. It’s actually available even when you’re not using one of the instanced drawing functions—it’ll just be zero in those cases. This means that you can use the same shaders for instanced and noninstanced rendering.

You can use gl_InstanceID to index into arrays that are the same length as the number of instances that you’re rendering. For example, you can use it to look up texels in a texture or to index into a uniform array. Really, what you’d be doing though is treating the array as if it were an “instanced attribute.” That is, a new value of the attribute is read for each instance you’re rendering. OpenGL can feed this data to your shader automatically using a feature called instanced arrays. To use instanced arrays, declare an input to your shader as normal. The input attribute will have an index that you would use in calls to functions like glVertexAttribPointer. Normally, the vertex attributes would be read per vertex and a new value would be fed to the shader. However, to make OpenGL read attributes from the arrays once per instance, you can call

void glVertexAttribDivisor(GLuint index, GLuint divisor);

Pass the index of the attribute to the function in index and set divisor to the number of instances you’d like to pass between each new value being read from the array. If divisor is zero, then the array becomes a regular vertex attribute array with a new value read per vertex. If divisor is nonzero, however, then new data is read from the array once every few instances. For example, if you set divisor to one, you’ll get a new value from the array for each instance. If you set divisor to two, you’ll get a new value for every second instance, and so on. You can mix and match the divisors, setting different values for each attribute.

An example of using this functionality would be when you want to draw a set of objects with different colors. Consider the simple vertex shader in Listing 5.

Listing 5. Simple Vertex Shader with Per-Vertex Color
#version 150

precision highp float;

in vec4 position;
in vec4 color;

out Fragment
{
    vec4 color;
} fragment;

uniform mat4 mvp;

void main(void)
{
    gl_Position = mvp * position;
    fragment.color = color;
}

Normally, the attribute color would be read once per vertex, and so every vertex would end up having a different color. The application would have to supply an array of colors with as many elements as there were vertices in the model. Also it wouldn’t be possible for every instance of the object to have a different color because the shader doesn’t know anything about instancing. We can make color an instanced array if we call

glVertexAttribDivisor(index_of_color, 1);

where index_of_color is the index of the slot to which the color attribute has been bound.

Now, a new value of color will be fetched from the vertex array once per instance. Every vertex within any particular instance will receive the same value for color, and the result will be that each instance of the object will be rendered in a different color. The size of the vertex array holding the data for color only needs to be as long as the number of indices we want to render. If we increase the value of the divisor, new data will be read from the array with less and less frequency. If the divisor is two, a new value of color will be presented every second instance; if the divisor is three, color will be updated every third instance; and so on.

If we render geometry using this simple shader, each instance will be drawn on top of the others. We need to modify the position of each instance so that we can see each one. We can use another instanced array for this. Listing 6 shows a simple modification to the vertex shader in Listing 5.

Listing 6. Simple Instanced Vertex Shader
#version 150

precision highp float;

in vec4 position;
in vec4 instance_color;
in vec4 instance_position;

out Fragment
{
    vec4 color;
} fragment;

uniform mat4 mvp;

void main(void)
{
    gl_Position = mvp * (position + instance_position);
    fragment.color = instance_color;
}

Now, we have a per-instance position as well as a per-vertex position. We can add these together in the vertex shader before multiplying with the model-view-projection matrix. We can set the instance_position input attribute to an instanced array by calling

glVertexAttribDivisor(index_of_instance_position, 1);

Again, index_of_instance_position is the index of the location to which the instance_position attribute has been bound. Any type of input attribute can be made instanced using glVertexAttribDivisor. This example is simple and only uses a translation (the value held in instance_position). A more advanced application could use matrix vertex attributes or pack some transformation matrices into uniforms and pass matrix weights in instanced arrays. The application can use this to render an army of soldiers, each with a different pose, or a fleet of spaceships all flying in different directions.

Now let’s hook this simple shader up to a real program. First, we load our shaders and set the attribute positions like normal before linking the program as shown in Listing 7.

Listing 7. Setting Up Instanced Attributes
instancingProg = gltLoadShaderPair("instancing.vs", "instancing.fs");
glBindAttribLocation(instancingProg, 0, "position");
glBindAttribLocation(instancingProg, 1, "instance_color");
glBindAttribLocation(instancingProg, 2, "instance_position");
glLinkProgram(instancingProg);

In Listing 8, we declare some data and load it into a vertex buffer (attached to a vertex array object).

Listing 8. Getting Ready for Instanced Rendering
static const GLfloat square_vertices[] =
{
    -1.0f, -1.0f, 0.0f, 1.0f,
     1.0f, -1.0f, 0.0f, 1.0f,
     1.0f,  1.0f, 0.0f, 1.0f,
    -1.0f,  1.0f, 0.0f, 1.0f
};

static const GLfloat instance_colors[] =
{
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f
};

static const GLfloat instance_positions[] =
{
    -2.0f, -2.0f, 0.0f, 0.0f,
     2.0f, -2.0f, 0.0f, 0.0f,
     2.0f,  2.0f, 0.0f, 0.0f,
    -2.0f,  2.0f, 0.0f, 0.0f
};

GLuint offset = 0;

glGenVertexArrays(1, &square_vao);
glGenBuffers(1, &square_vbo);
glBindVertexArray(square_vao);
glBindBuffer(GL_ARRAY_BUFFER, square_vbo);
glBufferData(GL_ARRAY_BUFFER,
             sizeof(square_vertices) +
             sizeof(instance_colors) +
             sizeof(instance_positions), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, offset,
                sizeof(square_vertices),
                square_vertices);
offset += sizeof(square_vertices);
glBufferSubData(GL_ARRAY_BUFFER, offset,
                sizeof(instance_colors), instance_colors);
offset += sizeof(instance_colors);
glBufferSubData(GL_ARRAY_BUFFER, offset,
                sizeof(instance_positions), instance_positions);
offset += sizeof(instance_positions);

glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,
                      (GLvoid *)sizeof(square_vertices));
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0,
                      (GLvoid *)(sizeof(square_vertices) +
                                 sizeof(instance_colors)));

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);				  

Now all that remains is to set the vertex attrib divisors for the instance_color and instance_position attribute arrays.

glVertexAttribDivisor(1, 1);
glVertexAttribDivisor(2, 1);

Now we draw four instances of the geometry we put into our vertex buffer. Each instance consists of four vertices, each with its own position. The same vertex in each instance has the same position. However, all of the vertices in a single instance see the same value of instance_color and instance_position, and a new value of each is presented at each instance. Our rendering loop looks like this:

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);

glUseProgram(instancingProg);
glBindVertexArray(square_vao);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 4);

What we get is shown in Figure 6.

Figure 6. Result of instanced rendering.

In Figure 6, you can see that four squares have been rendered. Each is at a different position, and each has a different color. This can be extended to thousands or even millions of instances, and modern graphics hardware should be able to handle this without any issue.

Other  
 
Most View
Almost Here: Self-learning, Self-healing Computers (Part 1)
OCZ Agility 3 - Fast And Affordable
Sony NEX-F3 Review (Part 3)
Off The Shelf Or Self- Build? (Part 2)
Full-ATX Motherboard Gigabyte G1-SNIPER A88X
IPhone Cases Of The Year
Do You Really Need Security?
Magnepan DWM Woofer Review
Visual Basic 2010 : Setup & Deployment Projects for Windows Installer (part 1) - Creating a Setup Project
Genius LuxePad 9100 Bluetooth Tablet Keyboard
REVIEW
- First look: Apple Watch

- 10 Amazing Tools You Should Be Using with Dropbox
VIDEO TUTORIAL
- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 1)

- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 2)

- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 3)
Popular Tags
Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS
Top 10
PHP Tutorials : Storing Images in MySQL with PHP (part 2) - Creating the HTML, Inserting the Image into MySQL
PHP Tutorials : Storing Images in MySQL with PHP (part 1) - Why store binary files in MySQL using PHP?
Java Tutorials : Nested For Loop (part 2) - Program to create a Two-Dimensional Array
Java Tutorials : Nested For Loop (part 1)
C# Tutorial: Reading and Writing XML Files (part 2) - Reading XML Files
C# Tutorial: Reading and Writing XML Files (part 1) - Writing XML Files
How to use scanf in C.
Dynamic Arrays: Using malloc() and realloc()
10 Amazing Tools You Should Be Using with Dropbox
C# 2010 and the .NET 4 Platform : Understanding the ASP.NET Profile API (part 4) - Grouping Profile Data and Persisting Custom Objects