Search code examples
c++openglglfwglm-mathvao

Creating Mesh class and implementing does not generate model OpenGL?


I'm trying to create a Mesh class that can generate a model for me and then calling that model in the createTriangle function. However, whenever I try creating the meshObj1 and meshObj2 and then pushing it to the meshVector, it does not want to render? Why is this happening?

Mesh.h

#ifndef MESH_H
#define MESH_H

#include "GL/glew.h"

class Mesh {
    public:
        Mesh();
        ~Mesh();

        void createMesh(GLfloat *vertices, unsigned int *indices, unsigned int numOfVertices, unsigned int numOfIndices);
        void renderMesh();
        void clearMesh();


    private:
        GLuint VAO, VBO, IBO;
        GLsizei indexCount;
};

#endif

Mesh.cpp

#include "Mesh.h"

Mesh::Mesh() {
    VAO = 0;
    VBO = 0;
    IBO = 0;
    indexCount = 0;
}

Mesh::~Mesh() {
    clearMesh();
}

void Mesh::createMesh(GLfloat* vertices, unsigned int* indices, unsigned int numOfVertices, unsigned int numOfIndices) {
    indexCount = numOfIndices;

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

    //Information
    //VBO Information
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0] * numOfVertices), vertices, GL_STATIC_DRAW);

    //IBO Information
    glGenBuffers(1, &IBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * numOfIndices, indices, GL_STATIC_DRAW);


    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    //Unbinding
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}

void Mesh::renderMesh() {
    //Binding
    glBindVertexArray(VAO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

    //Rendering
    glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);

    //Unbinding
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void Mesh::clearMesh() {
    if (VAO != 0) {
        glDeleteVertexArrays(1, &VAO);
        VAO = 0;
    }

    if (VBO != 0) {
        glDeleteBuffers(1, &VBO);
        VBO = 0;
    }

    if (IBO != 0) {
        glDeleteBuffers(1, &IBO);
        IBO = 0;
    }

    indexCount = 0; 
}

Main.cpp

#include "GL/glew.h"
#include "GLFW/glfw3.h"

#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

#include <iostream>
#include <vector>
#include <memory>

#include "Mesh.h"


//Window dimensions
const GLint SCREEN_WIDTH = 1280, SCREEN_HEIGHT = 900;
GLuint shader, uniformModel, uniformProjection;

bool isMovingLeft = true; 
float triOffset = 0.f;
float triMaxOffset = 0.7;
float triIncrement = 0.005;


std::vector<Mesh*> meshVector;


//Vertex shader
static const char* vShader = "                                      \n\
    #version 460                                                    \n\
                                                                    \n\
    layout(location = 0) in vec3 pos;                               \n\
                                                                    \n\
    out vec4 vColor;                                                \n\
                                                                    \n\
    uniform mat4 model;                                             \n\
    uniform mat4 projection;                                        \n\
                                                                    \n\
    void main() {                                                   \n\
        gl_Position = projection * model * vec4(pos, 1.0);          \n\
        vColor = vec4(clamp(pos, 0.f, 1.f), 1.f);                   \n\
    };                                                              \n\
";

//Fragment shader
static const char* fShader = "                                      \n\
    #version 460                                                    \n\
                                                                    \n\
    in vec4 vColor;                                                 \n\
                                                                    \n\
    out vec4 color;                                                 \n\
                                                                    \n\
    void main() {                                                   \n\
        color = vColor;                                             \n\
    };                                                              \n\
";


void createTriangle() {
    unsigned int indices[] = {
        0, 3, 1,
        1, 3, 2,
        2, 3, 0,
        0, 1, 2
    };


    //Points of the triangle 
    GLfloat vertices[] = {
        -1.f, -1.f, 0.f, 
         0.f, -1.f, 1.f, 
         1.f, -1.f, 0.f, 
         0.f,  1.f, 0.f  
    };


    Mesh* meshObj1 = new Mesh();
    meshObj1->createMesh(vertices, indices, 12, 12);
    meshVector.push_back(meshObj1);

    Mesh* meshObj2 = new Mesh();
    meshObj2->createMesh(vertices, indices, 12, 12);
    meshVector.push_back(meshObj2);
}

void addShader(GLuint theProgram, const char* shaderCode, GLenum shaderType) {
    GLuint theShader = glCreateShader(shaderType);

    const GLchar* theCode[1];
    theCode[0] = shaderCode;

    GLint codeLength[1];
    codeLength[0] = strlen(shaderCode);

    glShaderSource(theShader, 1, theCode, codeLength);
    glCompileShader(theShader);


    //Getting error information for linking
    GLint result = 0;
    GLchar eLog[1024] = { 0 };

    glGetShaderiv(theShader, GL_COMPILE_STATUS, &result);
    if (result != GL_TRUE) {
        glGetShaderInfoLog(theShader, sizeof(eLog), NULL, eLog);
        std::cout << "Error compiling the " << shaderType << ' ' << eLog << '\n';
    }


    glAttachShader(theProgram, theShader);

}

void compileShaders() {
    shader = glCreateProgram();

    if (shader != GL_TRUE) {
        std::cout << "Shader program error!\n";
    }

    //Adding shaders
    addShader(shader, vShader, GL_VERTEX_SHADER);
    addShader(shader, fShader, GL_FRAGMENT_SHADER);

    //Getting error information for linking
    GLint result = 0;
    GLchar eLog[1024] = { 0 };

    //Linking shader
    glLinkProgram(shader);

    //Shader linking status
    glGetProgramiv(shader, GL_LINK_STATUS, &result);
    if (result != GL_TRUE) {
        glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
        std::cout << "Error linking program! " << eLog << '\n';
    }


    //Gets shader ID and then binds it with the variable inside shader
    uniformModel = glGetUniformLocation(shader, "model");
    uniformProjection = glGetUniformLocation(shader, "projection");
}


int main() {
    //Initialize GLFW
    if (glfwInit() != GLFW_TRUE) {
        std::cout << "GLFW init failed\n";
        glfwTerminate();
    }


    //Setup GLFW window properties
    //OpenGL version
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); //Large version
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); //Small version 
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //Detects any old OpenGL code, this will throw an error
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //Allows forward compatibility (between differnt OS) 


    //Creating window
    GLFWwindow* window;
    window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "OpenGL Test Window", NULL, NULL);
    glfwSetWindowPos(window, 250, 100);

    if (window == NULL) {
        std::cout << "GLFW window creation failed!\n";
        glfwTerminate();
    }

    //Get buffer size information
    int bufferWidth, bufferHeight;
    glfwGetFramebufferSize(window, &bufferWidth, &bufferHeight);

    //Set context for GLEW to use (can change between which window)
    glfwMakeContextCurrent(window);

    //Allow modern extension features
    glewExperimental = GL_TRUE;


    if (glewInit() != GLEW_OK) {
        std::cout << "Glew init failed!\n";
        glfwDestroyWindow(window);
        glfwTerminate();
    }



    glEnable(GL_DEPTH_TEST);

    //Setup viewport size
    glViewport(0, 0, bufferWidth, bufferHeight);


    createTriangle();
    compileShaders();


    glm::mat4 projection = glm::perspective(45.f, (GLfloat)bufferWidth / (GLfloat)bufferHeight, 0.1f, 100.f);



    //Main game loop
    while (!glfwWindowShouldClose(window)) {
        //Get + Handle user input events
        glfwPollEvents();

        //Left-Right
        if (isMovingLeft) {
            triOffset += triIncrement;
        }

        else {
            triOffset -= triIncrement;
        }


        if (abs(triOffset) >= triMaxOffset) {
            isMovingLeft = !isMovingLeft;
        }


        //Clear window
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

        glUseProgram(shader);

        //Matrix 4x4
        glm::mat4 model(1.f);
        model = glm::translate(model, glm::vec3(triOffset, 0.f, -2.5f));
        model = glm::scale(model, glm::vec3(0.4f, 0.4f, 1.f));
        glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(uniformProjection, 1, GL_FALSE, glm::value_ptr(projection));
        meshVector[0]->renderMesh();

        model = glm::mat4(1.f);
        model = glm::translate(model, glm::vec3(-triOffset, 1.f, -2.5f));
        model = glm::scale(model, glm::vec3(0.4f, 0.4, 1.f));
        meshVector[1]->renderMesh();


        glUseProgram(0);

        glfwSwapBuffers(window);
    }
}

I expect two pyramids to form with color.


Solution

  • The GL_ELEMENT_ARRAY_BUFFER target binding is stored in the Vertex Array Object. See Index buffers.

    This means, that the instruction

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    

    breaks the former binding of IBO to the vertex array object VAO. Remove this instruction from your code, to solve the issue.

    Note, GL_ARRAY_BUFFER and the GL_ELEMENT_ARRAY_BUFFER behave differently. The current GL_ARRAY_BUFFER binding is a global state, but the GL_ELEMENT_ARRAY_BUFFER binding is stated in the current Vertex Array Object.
    Also the GL_ARRAY_BUFFER is stated in the state vector of the VAO, but this happens when glVertexAttribPointer is called. When glVertexAttribPointer is called, then the buffer which is currently bound to the target GL_ARRAY_BUFFER is associated to the vertex attribute with the specified index.

    That causes the difference in the behavior of GL_ELEMENT_ARRAY_BUFFER and GL_ARRAY_BUFFER. A VAO can only refer to 1 index (element) buffer, but it can refer to multiple array buffers. Each attribute (index) can be associated to a different buffer.


    Before you render the 2nd mesh, you've the set the corresponding model matrix to the default uniform block of the installed program:

    glUseProgram(shader);
    glUniformMatrix4fv(uniformProjection, 1, GL_FALSE, glm::value_ptr(projection));
    
    glm::mat4 model(1.f);
    model = glm::translate(model, glm::vec3(triOffset, 0.f, -2.5f));
    model = glm::scale(model, glm::vec3(0.4f, 0.4f, 1.f));
    glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(model));        
    meshVector[0]->renderMesh();
    
    model = glm::mat4(1.f);
    model = glm::translate(model, glm::vec3(-triOffset, 1.f, -2.5f));
    model = glm::scale(model, glm::vec3(0.4f, 0.4, 1.f));
    glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(model)); // <---
    meshVector[1]->renderMesh();