1

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.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Siddharth
  • 13
  • 2
  • 3
    You created EBO. Array `indices` was bound to this EBO. So what is the intention of passing `indices` as last parameter of `glDrawElements` ? It is an offset to starting index inside EBO buffer itself, and it should be nullptr/0 in your case. – rafix07 May 08 '23 at 10:12
  • According to the example [here](https://docs.gl/gl3/glDrawElements), you need to use `glVertexAttribPointer` before `glDrawElements` (and `glDisableVertexAttribArray` afterwards). – wohlstad May 08 '23 at 10:16
  • 1
    @wohlstad when using distinct VAOs for different sub-/buffer regions, calling `glVertexAttribPointer` before `glDraw…` is superfluous. OP is using a VAO so your suggestion in *this case* is bogus. – datenwolf May 08 '23 at 11:46
  • @datenwolf understood. Found the example in a quick search and thought it might help, but apparently that's no the case. – wohlstad May 08 '23 at 11:50
  • @wohlstad the reason you can find this code in so many examples is, that VAOs turned out not to be that much of a "great" idea in hindsight. When they got introduced, using them created a __HUGE__ performance hit, so what everyone did then (and often still today) is to just create and bind a dummy VAO right after OpenGL context creation, and then leave it bound for the rest of the program and no longer bother with it at all. All of the remaining GL code would be done like in the days before VAOs were a thing. – datenwolf May 08 '23 at 11:54
  • @rafix07 Thanks! that indeed was the issue. It turns out that some docs for OpenGL are a bit misleading. – Siddharth May 08 '23 at 13:30

1 Answers1

1

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.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Yun
  • 3,056
  • 6
  • 9
  • 28
  • 3
    Yes, that's how old OpenGL worked. Back then you could have data be held in either "host" memory (i.e. CPU address space) or "server" memory (roughly GPU address space, albeit there were some additional nuances) and whenever you were referring to "host" memory you'd supply an absolute address held in process address space, while for "server" memory you'd supply an offset (relative to `NULL`/`nullptr`). – datenwolf May 08 '23 at 11:44
  • @datenwolf Thank you so much! If you want to, please feel free to add your own answer, or edit this info into my answer and crediting yourself. – Yun May 08 '23 at 11:49
  • 3
    I already wrote a much longer in depth answer about that, over 10 years ago. Maybe just link to that :-) https://stackoverflow.com/a/8284829/524368 – datenwolf May 08 '23 at 11:57
  • 1
    OMG that makes so much sense! Thanks a lot for your reply, appreciate it. And yeah, the documentation and the autocorrect in VS are very confusing for a beginner. – Siddharth May 08 '23 at 13:03