I am trying to use VBO and Instancing mechanism the most efficent way. I have a world based on voxels and I would like to draw them using least possible number of draw-calls. The code below prepares VBO with a quad:
void VoxelView::initVBOs() {
/*--------------------- Main OpenGL Program ---------------------*/
/* Vertices of a triangle (counter-clockwise winding) */
float data[6][3] = {
// Left bottom triangle
{ -0.5f, 0.5f, 0.0f },
{ -0.5f, -0.5f, 0.0f },
{ 0.5f, -0.5f, 0.0f },
// Right top triangle
{ 0.5f, -0.5f, 0.0f },
{ 0.5f, 0.5f, 0.0f },
{ -0.5f, 0.5f, 0.0f }
};
/*---------------------- Initialise VBO - (Note: do only once, at start of program) ---------------------*/
/* Create a new VBO and use the variable "triangleVBO" to store the VBO id */
glGenBuffers(1, &triangleVBO);
/* Make the new VBO active */
glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
/* Upload vertex data to the video device */
glBufferData(GL_ARRAY_BUFFER, 6 * 3 * sizeof(float), data, GL_STATIC_DRAW);
/* Specify that our coordinate data is going into attribute index 0(shaderAttribute), and contains three floats per vertex */
glVertexAttribPointer(shaderAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
/* Enable attribute index 0(shaderAttribute) as being used */
glEnableVertexAttribArray(shaderAttribute);
/* Make the new VBO active. */
glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
/*-------------------------------------------------------------------------------------------------------*/
/*--------------------- Load Vertex and Fragment shaders from files and compile them --------------------*/
/* Read our shaders into the appropriate buffers */
vertexSource = filetobuf("Shaders/exampleVertexShader1.vert");
fragmentSource = filetobuf("Shaders/exampleFragmentShader1.frag");
/* Assign our handles a "name" to new shader objects */
vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
/* Associate the source code buffers with each handle */
glShaderSource(vertexShader, 1, (const GLchar**)&vertexSource, 0);
glShaderSource(fragmentShader, 1, (const GLchar**)&fragmentSource, 0);
/* Free the temporary allocated memory */
free(vertexSource);
free(fragmentSource);
/* Compile our shader objects */
glCompileShader(vertexShader);
glCompileShader(fragmentShader);
/*-------------------------------------------------------------------------------------------------------*/
/*-------------------- Create shader program, attach shaders to it and then link it ---------------------*/
/* Assign our program handle a "name" */
shaderProgram = glCreateProgram();
/* Attach our shaders to our program */
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
/* Bind attribute index 0 (shaderAttribute) to in_Position*/
/* "in_Position" will represent "data" array's contents in the vertex shader */
glBindAttribLocation(shaderProgram, shaderAttribute, "in_Position");
/* Link shader program*/
glLinkProgram(shaderProgram);
I am rendering the quad the following way:
void VoxelView::renderVBO()
{
/* Set shader program as being actively used */
glUseProgram(shaderProgram);
/* Set background colour to BLACK */
glClearColor(0.0, 0.0, 0.0, 1.0);
/* Clear background with BLACK colour */
glClear(GL_COLOR_BUFFER_BIT);
/* Actually draw the triangle, giving the number of vertices provided by invoke glDrawArrays
while telling that our data is a triangle and we want to draw 0-3 vertexes
*/
glDrawArrays(GL_TRIANGLES, 0, 6);
}
I would like to draw this quad (which uses VBO) multiple times using the instancing mechanizm. I would like it to fe fairly simple as I want to implement it for more sophisticated code. I know that I should use glDrawElementsInstanced method to use instancing but I don't know how to do it. Does anybody know how to do it?
When using glDrawElementsInstanced you need to make your shaders use gl_InstanceiD
#version 410
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in mat4 WVP;
layout (location = 7) in mat4 World;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
flat out int InstanceID;
void main()
{
gl_Position = WVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = World * vec4(Normal, 0.0)).xyz;
WorldPos0 = World * vec4(Position, 1.0)).xyz;
InstanceID = gl_InstanceID;
};
glDrawElementsInstanced
use gl_InstanceID variable as if it were a static integer vertex attribute. When the first copy of the vertices is sent to OpenGL,
gl_InstanceID
will be zero. It will then be incremented once for each copy of the geometry and will eventually reach instanceCount - 1.
It behaves like this
for (int n = 0; n < instancecount; n++)
{
// Set the value of gl_InstanceID
glVertexAttrib1i(gl_InstanceID, n); // except this is internally incremented and not a real vertex attribute
// Make a normal call to glDrawElements
glDrawElements(mode, count, type, indices);
}
Except that this happens internally, and you only need to call glDrawArraysInstanced()
once. In order to use different transforms for each instance you can pass an array of uniform matrices and index that using gl_InstanceID
or you can use texture buffer objects and store transforms there.
So your drawing code should become like this, but note that you still need to pass uniform matrices for each intsance in an array or pass a texture buffer object once for all.
glDrawElementsInstanced(primitivetype, indices, GL_UNSIGNED_INT, 0, instanceCount-1);
and your vertex shader should become
uniform mat4 instancematrices[32];
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * instancematrices[gl_InstanceID] * gl_Vertex;
}