Search code examples
c++openglglslglfwgldrawarrays

OpenGL Indexed Draw


I was trying to draw a Triangle with a custom FragmentShader for a gradient effect on the triangle.

I was trying to use Indexed-Drawing instead of Drawing Arrays.
Although glDrawArrays(...) works just fine, glDrawElements(...) doesn't work for me. I just get a blank screen with indexed-drawing as opposed to a normal output (Screenshots are attached below).

Could someone tell me where I'm going wrong?

(Here's my code)

#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>


// Vertex Shader source code
const char* vertexShaderSource = 
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"out vec3 outColor;\n"
"void main() {\n"
"    gl_Position = vec4(aPos, 1.0);\n"
"    outColor = aColor;\n"
"}\n\0";

// Fragment Shader source code
const char* fragmentShaderSource = 
"#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 outColor;\n"
"void main() {\n"
"    FragColor = vec4(outColor, 1.0f);\n"
"}\n\0";


const int width{ 800 };
const int height{ 600 };

bool WIREFRAME_MODE = false;


int main() {
    glfwInit();

#pragma region Creating a Window

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(width, height, "Triangle Exercise", nullptr, nullptr);
    if (!window) {
        std::cout << " >> Failed to create window!" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

#pragma endregion 

    gladLoadGL();

    glViewport(0, 0, width, height);

#pragma region Shader Compilation

    // Vertex Shader
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);

    // Fragment Shader
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);

    // Shader Program
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // Delete the shaders
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

#pragma endregion 

#pragma region Vertex and Index data

    // Vertex data
    GLfloat vertices[] = {
        // positions            // colors
         0.5f, -0.5f, 0.0f,     1.0f, 0.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,     0.0f, 1.0f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f,     0.0f, 0.0f, 1.0f   // top  
    };

    // Index data
    GLuint indices[] = {
        0, 1, 2
    };

#pragma endregion 

#pragma region VAO, VBO and EBO

    GLuint VAO;     // Vertex Attribute Object ref
    GLuint VBO;     // Vertex Buffer Object ref
    GLuint EBO;     // Element Buffer Object ref

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // Point to the X,Y,Z vertex data in the VBO (at layout location 0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * (sizeof(GLfloat)), (void*)0);           
    glEnableVertexAttribArray(0);

    // Point to the RGB color data in the VBO (at layout location 1)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * (sizeof(GLfloat)), (void*)(3*sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);               // unbind VBO
    glBindVertexArray(0);                           // unbind VAO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);       // unbind EBO (conatined in VAO)

#pragma endregion 

    // While window isn't closed
    while (!glfwWindowShouldClose(window)) {

        if (WIREFRAME_MODE) {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);      // Wireframe mode
        }
        else {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);      // Fill mode
        }

        glClearColor(0, 0, 0, 1);                           // black background
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);


        // WORKS FINE
        glDrawArrays(GL_TRIANGLES, 0, sizeof(vertices) / sizeof(GLfloat));

        // DOESN'T WORK?
        //
        //glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(GLuint), GL_UNSIGNED_INT, indices);
        //

        glfwSwapBuffers(window);

        glfwPollEvents();
    }

#pragma region Cleanup

    glDeleteProgram(shaderProgram);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &VAO);
    glDeleteBuffers(1, &EBO);
    glfwDestroyWindow(window);

#pragma endregion

    glfwTerminate();
    return 0;
}

Output screenshot with glDrawArrays():

glDrawArrays

Output screenshot with glDrawElements():

glDrawElements

I tried checking if my EBO's parameters were wrong, but they seem fine to me.


Solution

  • The last parameter of glDrawElements ("indices") should not be a pointer to the indices array, but "an offset of the first index in the array in the data store of the buffer" (ref), then cast to a GLvoid*. Here, the draw call should use all 3 indices, so the offset should be 0:

    glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(GLuint), GL_UNSIGNED_INT, static_cast<GLvoid*>(0));
    

    (Other, roughly equivalent expressions are also possible, such as 0, NULL and nullptr.)

    The confusion may have come from documentation such as this, where the parameter is incorrectly described as "a pointer to the location where the indices are stored". I suspect that this how glDrawElements worked in older OpenGL versions (e.g. OpenGL 1.1) and that since the use of the function has changed while the function prototype has remained the same, hence the oddity of having to cast byte offset to a void pointer.