3
\$\begingroup\$

I am an amateur in OpenGl and for this reason I am seeking to learn only modern OpenGl the 4.x stuff. Once I had completed basic tutorials (rotating cubes, for example) I decided I would try and create a voxel-based program dealing solely with cubes. The goals of this program was to be fast, use limited CPU power and memory, and be dynamic so the map size can change and blocks will only be drawn if in the array it says the block is filled.

I have one VBO with the vertices and indexes of a cube built out of triangles. At the beginning of the render function I tell OpenGl the shaders to use and then bind the VBO; once that is complete I execute a loop.

Draw Cube Loop:

//The letter_max are the dimensions of the matrix created to store the voxel status in
// The method I use for getting and setting entries in the map are very efficient so I have not included it in this example
for(int z = -(z_max / 2); z < z_max - (z_max / 2); z++)
{
    for(int y = -(y_max / 2); y < y_max - (y_max / 2); y++)
    {
        for(int x = -(x_max / 2); x < x_max - (x_max / 2); x++)
        {
            DrawCube(x, y, z);
        }
    }
} 

Cube.c

#include "include/Project.h"

void CreateCube()
{
    const Vertex VERTICES[8] =
    {
    { { -.5f, -.5f,  .5f, 1 }, { 0, 0, 1, 1 } },
    { { -.5f,  .5f,  .5f, 1 }, { 1, 0, 0, 1 } },
    { {  .5f,  .5f,  .5f, 1 }, { 0, 1, 0, 1 } },
    { {  .5f, -.5f,  .5f, 1 }, { 1, 1, 0, 1 } },
    { { -.5f, -.5f, -.5f, 1 }, { 1, 1, 1, 1 } },
    { { -.5f,  .5f, -.5f, 1 }, { 1, 0, 0, 1 } },
    { {  .5f,  .5f, -.5f, 1 }, { 1, 0, 1, 1 } },
    { {  .5f, -.5f, -.5f, 1 }, { 0, 0, 1, 1 } }
    };

    const GLuint INDICES[36] =
    {
    0,2,1,  0,3,2,
    4,3,0,  4,7,3,
    4,1,5,  4,0,1,
    3,6,2,  3,7,6,
    1,6,5,  1,2,6,
    7,5,6,  7,4,5
    };

    ShaderIds[0] = glCreateProgram();
    ExitOnGLError("ERROR: Could not create the shader program");
    {
    ShaderIds[1] = LoadShader("FragmentShader.glsl", GL_FRAGMENT_SHADER);
    ShaderIds[2] = LoadShader("VertexShader.glsl", GL_VERTEX_SHADER);
    glAttachShader(ShaderIds[0], ShaderIds[1]);
    glAttachShader(ShaderIds[0], ShaderIds[2]);
    }
    glLinkProgram(ShaderIds[0]);
    ExitOnGLError("ERROR: Could not link the shader program");

    ModelMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ModelMatrix");
    ViewMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ViewMatrix");
    ProjectionMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ProjectionMatrix");
    ExitOnGLError("ERROR: Could not get shader uniform locations");

    glGenVertexArrays(1, &BufferIds[0]);
    ExitOnGLError("ERROR: Could not generate the VAO");
    glBindVertexArray(BufferIds[0]);
    ExitOnGLError("ERROR: Could not bind the VAO");

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    ExitOnGLError("ERROR: Could not enable vertex attributes");

    glGenBuffers(2, &BufferIds[1]);
    ExitOnGLError("ERROR: Could not generate the buffer objects");

    glBindBuffer(GL_ARRAY_BUFFER, BufferIds[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES), VERTICES, GL_STATIC_DRAW);
    ExitOnGLError("ERROR: Could not bind the VBO to the VAO");

    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)0);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)sizeof(VERTICES[0].Position));
    ExitOnGLError("ERROR: Could not set VAO attributes");

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, BufferIds[2]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(INDICES), INDICES, GL_STATIC_DRAW);
    ExitOnGLError("ERROR: Could not bind the IBO to the VAO");

    glBindVertexArray(0);
}

void DestroyCube()
{
    glDetachShader(ShaderIds[0], ShaderIds[1]);
    glDetachShader(ShaderIds[0], ShaderIds[2]);
    glDeleteShader(ShaderIds[1]);
    glDeleteShader(ShaderIds[2]);
    glDeleteProgram(ShaderIds[0]);
    ExitOnGLError("ERROR: Could not destroy the shaders");

    glDeleteBuffers(2, &BufferIds[1]);
    glDeleteVertexArrays(1, &BufferIds[0]);
    ExitOnGLError("ERROR: Could not destroy the buffer objects");
}

void DrawCube(float x, float y, float z)
{
    ModelMatrix = IDENTITY_MATRIX;

    TranslateMatrix(&ModelMatrix, x, y, z);
    TranslateMatrix(&ModelMatrix, MainCamera.x, MainCamera.y, MainCamera.z);

    glUniformMatrix4fv(ModelMatrixUniformLocation, 1, GL_FALSE, ModelMatrix.m);
    glUniformMatrix4fv(ViewMatrixUniformLocation, 1, GL_FALSE, ViewMatrix.m);
    ExitOnGLError("ERROR: Could not set the shader uniforms");


    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (GLvoid*)0);
    ExitOnGLError("ERROR: Could not draw the cube");
}

Here is a link to the github repository with the complete code.

The vertex shader only handles rotation and transformation of vertices and the fragment shader only deals with colour - they are not expensive to run so they are not the bottleneck.

How can this code be improved to render more efficiently and take full advantage of modern OpenGL features to decrease overhead?

P.S. I am not looking for a book or a tool or an off-site resource as an answer. I have used backface culling and the OpenGL depth test to try and improve speed, however they haven't made a dramatic difference - it is still taking ~50ms to render a frame and that is too much for a voxel grid of 32*32*32.

\$\endgroup\$
  • \$\begingroup\$ Code does not compile . Best to post compilable code. \$\endgroup\$ – chux Jan 5 '18 at 20:32
  • \$\begingroup\$ Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate. \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jan 5 '18 at 21:08
  • \$\begingroup\$ @chux I have edited the question and added a link to my github repo with all of the code. \$\endgroup\$ – Oliver Strong Jan 5 '18 at 22:14
5
\$\begingroup\$

Your question is very broad and without enough code to do anything else than speculate. Regardless, I will venture an answer based on speculation. I believe that how to best improve your performance has actually nothing to do with OpenGL but rather how you render your scene.

Or to be more specific, how to not render parts of the scene. To me it seems as if you're rendering every cube in your voxel grid, even if they are covered by other cubes and thus not visible at all. You need to process your voxel grid to mark voxels that are concealed as not visible and simply not draw them.

Next you should consider not drawing sides of your cube that are covered by another cube.

Eventually you will get to a point where traversing the grid to find what to render is the most expensive part of your render loop. And at that point you need to start considering to split your voxel grid into larger chunks that can be individually processed into a single vbo mesh that you can render in one call instead of a call for every cube.

As always when dealing with performance, measure at every step and profile your code to see where the time is spent.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.