Search code examples
c++openglglslglfwglm-math

How to create two shapes using OpenGL and shaders


I am trying to create a cylinder and plane to put underneath cylinder. Before I created a new vertex buffer object for the plane, the cylinder was rendering just fine. But for some reason, neither the cylinder or the plane are rendering when I run the program. I have created an EBO for the cylinder, but I am still getting a black window. I don't know if I need to create an EBO for each shape and a VAO for each shape or if there is something wrong with my draw function. Can someone point out what may be the problem? Here is my code:

    #include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <iostream>

// GLM library 
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using namespace std;

int width, height;
const double PI = 3.14159;
const float toRadians = PI / 180.0f;

// Draw Primitive(s)
void draw() {
    GLenum mode = GL_TRIANGLES;

    GLsizei indices = 60;

    glDrawElements(mode, indices, GL_UNSIGNED_BYTE, nullptr);
}

// Create and Compile Shaders
static GLuint CompileShader(const string& source, GLuint shaderType) {

    // Create Shader Object
    GLuint shaderID = glCreateShader(shaderType);
    const char* src = source.c_str();

    // Attach source code to Shader object
    glShaderSource(shaderID, 1, &src, nullptr);

    // Compile Shader
    glCompileShader(shaderID);

    // Return ID of Compiled shader
    return shaderID;
}

// Create Program Object
static GLuint CreateShaderProgram(const string& vertexShader, const string& fragmentShader) {

    // Compile vertex shader
    GLuint vertexShaderComp = CompileShader(vertexShader, GL_VERTEX_SHADER);

    // Compile fragment shader
    GLuint fragmentShaderComp = CompileShader(fragmentShader, GL_FRAGMENT_SHADER);

    // Create program object
    GLuint shaderProgram = glCreateProgram();

    // Attch vertex and fragment shaders to program object
    glAttachShader(shaderProgram, vertexShaderComp);
    glAttachShader(shaderProgram, fragmentShaderComp);

    // Link shaders to create executable
    glLinkProgram(shaderProgram);

    // Delete compiled vertex and fragment shaders
    glDeleteShader(vertexShaderComp);
    glDeleteShader(fragmentShaderComp);

    // Return Shader Program
    return shaderProgram;
}

int main(void) {

    width = 640; height = 480;

    GLFWwindow* window;

    // Initialize the library
    if (!glfwInit())
        return -1;

    // Create a windowed mode window and its OpenGL context
    window = glfwCreateWindow(width, height, "Main Window", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }

    // Make the window's context current
    glfwMakeContextCurrent(window);

    // Initialize GLEW
    if (glewInit() != GLEW_OK)
        cout << "Error!" << endl;

    GLfloat cylinderVertices[] = {

        // Base of the cylinder     
        // Triangle One                                     // Color
        0.0,                  0.0,                  0.0, 1.0, 0.0, 0.0, // Vertex 0  red
        cos(0 * toRadians), sin(0 * toRadians), 0.0, 0.0, 1.0, 0.0, // Vertex 1  green
        cos(60 * toRadians), sin(60 * toRadians), 0.0, 0.0, 0.0, 1.0, // Vertex 2  blue
        // Part of Triangle Two
        cos(120 * toRadians), sin(120 * toRadians), 0.0, 1.0, 0.0, 1.0, // Vertex 3  purple
        // Part of Triangle Three
        cos(180 * toRadians), sin(180 * toRadians), 0.0, 0.0, 1.0, 0.0, // Vertex 4  greem
        // Part of Triangle Four
        cos(240 * toRadians), sin(240 * toRadians), 0.0, 0.0, 0.0, 1.0, // Vertex 5  blue
        // Part of Triangle Five
        cos(300 * toRadians), sin(300 * toRadians), 0.0, 1.0, 0.0, 1.0, // Vertex 6  purple
        // Part of Triangle Six
        cos(360 * toRadians), sin(360 * toRadians), 0.0, 0.0, 1.0, 0.0,  // Vertex 7  green

        // Sides of the cylinder
        // Part of Triangle Seven
        cos(0 * toRadians), sin(0 * toRadians), 2.0, 1.0, 0.0, 0.0, // Vertex 8 red
        // Part of Triangle Eight
        cos(60 * toRadians), sin(60 * toRadians), 2.0, 0.0, 1.0, 0.0, // Vertex 9 green
        // Part of Triangle Nine
        cos(120 * toRadians), sin(120 * toRadians), 2.0, 0.0, 0.0, 1.0, // Vertex 10 blue
        // Part of Triangle Ten
        cos(180 * toRadians), sin(180 * toRadians), 2.0, 1.0, 0.0, 1.0, // Vertex 11 purple
        // Part of Triangle Eleven
        cos(240 * toRadians), sin(240 * toRadians), 2.0, 1.0, 0.0, 0.0, // Vertex 12 red
        // Part of Triangle Twelve
        cos(300 * toRadians), sin(300 * toRadians), 2.0, 0.0, 1.0, 0.0, // Vertex 13 green

    };

    // Define element indices 
    GLubyte cylinderIndices[] = {
        // Bottom base
        0,1,2,
        0,2,3,
        0,3,4,
        0,4,5,
        0,5,6,
        0,6,7,
        // Sides
        1,2,8,
        2,9,8,
        2,3,9,
        3,10,9,
        3,4,10,
        4,11,10,
        5,11,4,
        5,12,11,
        5,6,12,
        6,13,12,
        6,1,13,
        1,8,13
    };

    GLfloat planeVertices[] = {
        // positon attributes (x,y,z)
         0.0f, 0.0f, 0.0f, // vert 14
         0.0f, 1.0f, 0.0f, // red

        0.0f, 0.866f, 0.0f, // vert 15
        0.0f, 1.0f, 0.0f, // green

        1.0f, 0.0f, 0.0f,  // vert 16
        0.0f, 1.0f, 0.0f, // blue

        1.0f, 0.866f, 0.0f, // vert 17
        1.0f, 0.0f, 1.0f  // purple
    };

    // Define element indices
    GLubyte planeIndices[] = {
        14,16,15,
        16,17,15

    };

    // Enable Depth Buffer
    glEnable(GL_DEPTH_TEST);

    // Wireframe mode
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    GLuint cylinderVBO, planeVBO, EBO, VAO;

    glGenBuffers(1, &cylinderVBO); // Create VBO and returns ID
    glGenBuffers(1, &planeVBO);
    glGenBuffers(1, &EBO); // Create EBO

    glGenVertexArrays(1, &VAO); // Create VAO
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, cylinderVBO); // Select VBO and activate buffer
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); // Select EBO

    glBufferData(GL_ARRAY_BUFFER, sizeof(cylinderVertices), cylinderVertices, GL_STATIC_DRAW); // Load vertex attributes
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cylinderIndices), cylinderIndices, GL_STATIC_DRAW); // Load indices attributes

    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW); // Load vertex attributes
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(planeIndices), planeIndices, GL_STATIC_DRAW); // Load indices attributes

    // Specify attributes location and layout to GPU
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    // Color attribute location and layout
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glBindVertexArray(0);

    // Vertex shader source code
    string vertexShaderSource =
        "#version 330 core\n"
        "layout(location = 0) in vec4 vPosition;"
        "layout(location = 1) in vec4 aColor;"
        "out vec4 oColor;"
        "uniform mat4 model;"
        "uniform mat4 view;"
        "uniform mat4 projection;"
        "void main()\n"
        "{\n"
        "gl_Position = projection * view * model * vPosition;"
        "oColor = aColor;"
        "}\n";

    // Fragment shader source code
    string fragmentShaderSource =
        "#version 330 core\n"
        "in vec4 oColor;"
        "out vec4 fragColor;"
        "void main()\n"
        "{\n"
        "fragColor = oColor;"
        "}\n";

    // Creating Shader Program
    GLuint shaderProgram = CreateShaderProgram(vertexShaderSource, fragmentShaderSource);

    while (!glfwWindowShouldClose(window)) {

        // Resize window and graphics simultaneously
        glfwGetFramebufferSize(window, &width, &height);
        glViewport(0, 0, width, height);

        // Render here
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Use Shader Program exe and select VAO before drawing
        glUseProgram(shaderProgram); // Call Shader per-frame when updating attributes

        // Declare identity matrix 
        glm::mat4 modelMatrix(1.0f);
        glm::mat4 viewMatrix(1.0f);
        glm::mat4 projectionMatrix(1.0f);

        // Initialize transforms
        modelMatrix = glm::scale(modelMatrix, glm::vec3(0.5f, 0.5f, 0.5f));

        // I increased the third argument from -3.0f to -6.0f to make the object smaller
        // Moved the cup to the right by increasing the x coordinate
        viewMatrix = glm::translate(viewMatrix, glm::vec3(0.5f, 0.0f, -6.0f));
        // I changed up somme of the arguments, so the object would tilt right instead of toward me
        viewMatrix = glm::rotate(viewMatrix, 45.0f * toRadians, glm::vec3(-0.5f, 1.0f, 1.5f));

        projectionMatrix = glm::perspective(45.0f * toRadians, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);

        // Select uniform shader and variable
        GLuint modelLoc = glGetUniformLocation(shaderProgram, "model");
        GLuint viewLoc = glGetUniformLocation(shaderProgram, "view");
        GLuint projectionLoc = glGetUniformLocation(shaderProgram, "projection");

        // Pass transform to Shader

        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(viewMatrix));
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projectionMatrix));

        glBindVertexArray(VAO); // User-defined VAO must be called before draw

        for (GLuint i = 0; i < 4; i++) {

            glm::mat4 modelMatrix(1.0f);

            glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(modelMatrix));

            // Draw primitive(s)
            draw();

        }

        // Unbind Shader exe and VOA after drawing per frame
        glBindVertexArray(0); // In case different VAO will be used after
        glUseProgram(0); // In case different shader will be used after

        glBindVertexArray(VAO);

        // Swap front and back buffers
        glfwSwapBuffers(window);

        // Poll for and process events
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

Solution

  • Ok, so there is a bit of a structural problem with your plane & cylinder buffer layouts (ind indexing).

    Vertex Buffers

    First, let's start with the vertex data and associated buffers (we'll worry about indexing later).

    You have 2 choices for how to send the data to the GPU.

    1. Seperate vertex buffers for each shape. i.e. specify a cylinderVBO (with all cylinder vertices) and a planeVBO (with all plane vertices).
    2. Combine both sets of vertices into a single vertex buffer.

    If you are specifying the buffers separately, then setup EACH buffer in turn.

        // do the cylinder buffer first!
        glGenBuffers(1, &cylinderVBO); 
        glBindBuffer(GL_ARRAY_BUFFER, cylinderVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(cylinderVertices), cylinderVertices, GL_STATIC_DRAW); // Load vertex attributes
    
        // NOW do the plane buffer!
        glGenBuffers(1, &planeVBO); 
        glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW); // Load vertex attributes
    

    If you want to combine the buffers into one big blob, then generate one big array:

        GLfloat allVertices[] = {
    
            // -------------  cylinderVertices[]
    
            // Base of the cylinder     
            // Triangle One                                     // Color
            0.0,                  0.0,                  0.0, 1.0, 0.0, 0.0, // Vertex 0  red
            cos(0 * toRadians), sin(0 * toRadians), 0.0, 0.0, 1.0, 0.0, // Vertex 1  green
            cos(60 * toRadians), sin(60 * toRadians), 0.0, 0.0, 0.0, 1.0, // Vertex 2  blue
            // Part of Triangle Two
            cos(120 * toRadians), sin(120 * toRadians), 0.0, 1.0, 0.0, 1.0, // Vertex 3  purple
            // Part of Triangle Three
            cos(180 * toRadians), sin(180 * toRadians), 0.0, 0.0, 1.0, 0.0, // Vertex 4  greem
            // Part of Triangle Four
            cos(240 * toRadians), sin(240 * toRadians), 0.0, 0.0, 0.0, 1.0, // Vertex 5  blue
            // Part of Triangle Five
            cos(300 * toRadians), sin(300 * toRadians), 0.0, 1.0, 0.0, 1.0, // Vertex 6  purple
            // Part of Triangle Six
            cos(360 * toRadians), sin(360 * toRadians), 0.0, 0.0, 1.0, 0.0,  // Vertex 7  green
    
            // Sides of the cylinder
            // Part of Triangle Seven
            cos(0 * toRadians), sin(0 * toRadians), 2.0, 1.0, 0.0, 0.0, // Vertex 8 red
            // Part of Triangle Eight
            cos(60 * toRadians), sin(60 * toRadians), 2.0, 0.0, 1.0, 0.0, // Vertex 9 green
            // Part of Triangle Nine
            cos(120 * toRadians), sin(120 * toRadians), 2.0, 0.0, 0.0, 1.0, // Vertex 10 blue
            // Part of Triangle Ten
            cos(180 * toRadians), sin(180 * toRadians), 2.0, 1.0, 0.0, 1.0, // Vertex 11 purple
            // Part of Triangle Eleven
            cos(240 * toRadians), sin(240 * toRadians), 2.0, 1.0, 0.0, 0.0, // Vertex 12 red
            // Part of Triangle Twelve
            cos(300 * toRadians), sin(300 * toRadians), 2.0, 0.0, 1.0, 0.0, // Vertex 13 green
    
            // -------------  planeVertices[]
    
            // positon attributes (x,y,z)
             0.0f, 0.0f, 0.0f, // vert 14
             0.0f, 1.0f, 0.0f, // red
    
            0.0f, 0.866f, 0.0f, // vert 15
            0.0f, 1.0f, 0.0f, // green
    
            1.0f, 0.0f, 0.0f,  // vert 16
            0.0f, 1.0f, 0.0f, // blue
    
            1.0f, 0.866f, 0.0f, // vert 17
            1.0f, 0.0f, 1.0f  // purple
        };
    
        // Now generate a vertex buffer with all vertex data in one buffer
        GLuint allVerticesVBO;
        glGenBuffers(1, & allVerticesVBO); 
        glBindBuffer(GL_ARRAY_BUFFER, allVerticesVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(allVertices), allVertices, GL_STATIC_DRAW); // Load vertex attributes
    

    Index Buffers

    Now, depending on how you set the vertex buffers up previously, you will have to do one (but not both!) of the following.

    If you have separated your vertex buffers (one for cylinder, one for the plane), then you will need separate index buffers as well.

        // we need two index buffers, one for the plane, one for the cylinder
        GLuint cylinderIBO, planeIBO;
    
        //--------------------
        // cylinder
        //--------------------
    
        // Define element indices 
        GLubyte cylinderIndices[] = {
            // Bottom base
            0,1,2,
            0,2,3,
            0,3,4,
            0,4,5,
            0,5,6,
            0,6,7,
            // Sides
            1,2,8,
            2,9,8,
            2,3,9,
            3,10,9,
            3,4,10,
            4,11,10,
            5,11,4,
            5,12,11,
            5,6,12,
            6,13,12,
            6,1,13,
            1,8,13
        };
    
        glGenBuffers(1, &cylinderIBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cylinderIBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cylinderIndices), cylinderIndices, GL_STATIC_DRAW); // Load indices attributes
    
        //--------------------
        // plane
        //--------------------
        GLubyte planeIndices[] = {
            0,2,1, //< NOTE: These must start at ZERO in this case!
            2,3,1
    
        };
    
        glGenBuffers(1, &planeIBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planeIBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(planeIndices), planeIndices, GL_STATIC_DRAW); // Load indices attributes
    

    If however you wish to combine both shapes into a single buffer, then instead do this:

        // we need one index buffer!
        GLuint allIndicesIBO;
    
    
        // Define element indices 
        GLubyte allIndices[] = {
    
            //--------------------
            // cylinder
            //--------------------
    
            // Bottom base
            0,1,2,
            0,2,3,
            0,3,4,
            0,4,5,
            0,5,6,
            0,6,7,
            // Sides
            1,2,8,
            2,9,8,
            2,3,9,
            3,10,9,
            3,4,10,
            4,11,10,
            5,11,4,
            5,12,11,
            5,6,12,
            6,13,12,
            6,1,13,
            1,8,13, 
    
            //--------------------
            // plane
            //--------------------
    
            14,16,15, //< NOTE: these now start at 14!
            16,17,15
        };
    
        glGenBuffers(1, &allIndicesIBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, allIndicesIBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(allIndices), allIndices, GL_STATIC_DRAW); // Load indices attributes
    

    Vertex Buffer Objects

    If you have set up separate buffers for each shape, then you have two choices in how you set up the VAOs.

    1.1 Create two VAO's, one for each shape.

        GLuint cylinderVAO, planeVAO;
    
        // generate a bind new VAO for the cylinder
        glGenVertexArrays(1, & cylinderVAO); 
        glBindVertexArray(cylinderVAO);
    
        // specify which buffers the current VAO should use
        glBindBuffer(GL_ARRAY_BUFFER, cylinderVBO); 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cylinderIBO); 
    
        // Specify attributes location and layout to GPU
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
    
        // Color attribute location and layout
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
    
    
        // generate a bind new VAO for the plane
        glGenVertexArrays(1, &planeVAO); 
        glBindVertexArray(planeVAO);
    
        // specify which buffers the current VAO should use
        glBindBuffer(GL_ARRAY_BUFFER, planeVBO); 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planeIBO); 
    
        // Specify attributes location and layout to GPU
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
    
        // Color attribute location and layout
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
    

    To render the data like this, you'll need 2 draw calls, one for each shape.

        glBindVertexArray(cylinderVAO);
        glDrawElements(mode, 54, GL_UNSIGNED_BYTE, nullptr);
        
        glBindVertexArray(planeVAO);
        glDrawElements(mode, 6, GL_UNSIGNED_BYTE, nullptr);
    

    1.2 Setup 1 VAO, but rebind the vertex & index buffers on the fly.

        GLuint sharedVAO;
    
        // generate a bind new VAO for the cylinder
        glGenVertexArrays(1, & sharedVAO); 
        glBindVertexArray(sharedVAO);
    
        // Specify attributes location and layout to GPU
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
    
        // Color attribute location and layout
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
    

    To render the data like this, you'll need 2 draw calls, one for each shape.

        // specify the shared vertex format
        glBindVertexArray(sharedVAO);
    
        // specify data sources for cylinder, and render
        glBindBuffer(GL_ARRAY_BUFFER, cylinderVBO); 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cylinderIBO); 
        glDrawElements(mode, 54, GL_UNSIGNED_BYTE, nullptr);
        
        // specify data sources for plane, and render
        glBindBuffer(GL_ARRAY_BUFFER, planeVBO); 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planeIBO); 
        glDrawElements(mode, 6, GL_UNSIGNED_BYTE, nullptr);
    
    1. If however you have specified all of the data in a single vertex buffer, and a single index buffer, then instead you can do:
        GLuint sharedVAO;
    
        // generate a bind new VAO for the cylinder
        glGenVertexArrays(1, & sharedVAO); 
        glBindVertexArray(sharedVAO);
    
        // specify data sources for the VAO
        glBindBuffer(GL_ARRAY_BUFFER, allVerticesVBO); 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, allIndicesIBO); 
    
        // Specify attributes location and layout to GPU
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
    
        // Color attribute location and layout
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
    

    To render the data like this, you'll need 1 draw call.

        // render both shapes in one draw call
        glBindVertexArray(sharedVAO);
        glDrawElements(mode, 60, GL_UNSIGNED_BYTE, nullptr);
    

    And that's it.