-1

I wanted to implement an obj loader that would take 'v' (vertice), 'vt' (texture), 'vn' (normals), and 'f' (faces) coordinates from an obj file exported from blender and render it on my program. However, after implementing it I found that there was a problem with it. Nothing shows when I create it. Now, ever since I started, mesh code was always a problem for me so I'm hoping someone could find something that I cannot. Not only that but the texture code states (in terminal), Trying to access the pixels of an empty image. However, the image I give it has pixels and I have absolutely no idea why this is happening.

// Chest.cpp 
objLoader.loadObjModel("res/models/test.obj");
mesh.loadToVAO(objLoader.getVertices(), objLoader.getTextures(), objLoader.getNormals(), objLoader.getIndices());
mesh.loadTexture(Texture::get("res/images/wallpaper.png"));
program = Program::get(BASIC_VERTEX_SHADER, BASIC_FRAGMENT_SHADER);

This loads an obj model from test.obj and then in mesh.loadToVAO it gets the vertice, textures, normals, and indices. Then the mesh also gets the texture location.

// Mesh.cpp // Loading 
void Mesh::loadToVAO(std::vector<glm::vec3> vertices, std::vector<glm::vec2> textures, std::vector<glm::vec3> normals, std::vector<int> indices) {
    // create a VAO
    GLuint vaoID = createVAO();
    indicesSize = indices.size();
    bindIndicesBuffer(indices.data(), indicesSize);

    // Store the data in attribute lists
    storeDataInAttrubeList(0, 3, &vertices[0], vertices.size() * sizeof(glm::vec3));
    storeDataInAttrubeList(1, 2, &textures[0], textures.size() * sizeof(glm::vec2));
    storeDataInAttrubeList(2, 3, &normals[0], normals.size() * sizeof(glm::vec3));
    unbindVAO();
}

// Rendering
void Mesh::renderVAO() {
    // Texture
    texture->useTexture();

    // Binding
    glBindVertexArray(VAO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO);

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

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

// createVAO/storeDataInAttrubeList/bindIndicesBuffer
GLuint Mesh::createVAO() {
    GLuint vaoID;

    glGenVertexArrays(1, &vaoID);
    VAO = vaoID;
    glBindVertexArray(vaoID);

    return vaoID;
}

void Mesh::storeDataInAttrubeList(GLuint attribNumber, int attribSize, void* data, int dataSize) {
    GLuint vboID;
    // Create a new buffer
    glGenBuffers(1, &vboID);
    // Store the buffer in the list
    VBO = vboID;
    // Bind the buffer to use it
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    // Store the data in the buffer
    glBufferData(GL_ARRAY_BUFFER, dataSize, data, GL_STATIC_DRAW);
    // Tell OpenGL how and where to store this VBO in the VAO
    glVertexAttribPointer(attribNumber, attribSize, GL_FLOAT, GL_FALSE, 0, nullptr);
}

void Mesh::bindIndicesBuffer(int* indices, int& count) {
    GLuint vboID;
    // Generate a buffer and bind it for use
    glGenBuffers(1, &vboID);
    // Store the buffer in the list
    VBO = vboID;
    // Bind the buffer to use it
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboID);
    // Store the indices in the buffer
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * count, indices, GL_STATIC_DRAW);
} 

This goes through every line and finds the appropriate coordinate.

while (fgets(line, 256, file) != NULL) {
        token = NULL;
        type = strtok_s(line, " ", &token);

        // Vertex
        if (type[0] == 'v' && type[1] == NULL) {
            x = strtod(token, &stop);
            token = stop + 1; 
            y = strtod(token, &stop);
            token = stop + 1; 
            z = strtod(token, &stop);

            vertices.push_back(glm::vec3(x, y, z));
        }

        // Textures
        else if (type[0] == 'v' && type[1] == 't') {
            x = strtod(token, &stop);
            token = stop + 1; 
            y = 1 - strtod(token, &stop);

            tempTextures.push_back(glm::vec2(x, y));
        }

        // Normals
        else if (type[0] == 'v' && type[1] == 'n') {
            x = strtod(token, &stop);
            token = stop + 1; 
            y = strtod(token, &stop);
            token = stop + 1; 
            z = strtod(token, &stop);

            tempNormals.push_back(glm::vec3(x, y, z));
        }

        // Faces
        else if (type[0] == 'f') {
            if (indices.size() == 0) {
                // Set size of the array
                textures.resize(vertices.size());
                normals.resize(vertices.size());
            }

            // Process vertices data
            processVertices(token, indices, tempTextures, textures, tempNormals, normals);
        }
    }
// This method process the vertice and indice information 
void OBJLoader::processVertices(char* vertexData, std::vector<int>& indices, std::vector<glm::vec2>& tempTextures, 
    std::vector<glm::vec2>& textures, std::vector<glm::vec3>& tempNormals, std::vector<glm::vec3>& normals) {

    char* stop;
    int vertexPointer;

    for (unsigned int i = 0; i < 3; i++) {
        // Get and store index
        vertexPointer = strtol(vertexData, &stop, 10) - 1;
        indices.push_back(vertexPointer);
        vertexData = stop + 1; 

        textures[vertexPointer] = tempTextures[strtol(vertexData, &stop, 10) - 1];
        vertexData = stop + 1; 

        normals[vertexPointer] = tempNormals[strtol(vertexData, &stop, 10) - 1];
        vertexData = stop + 1; 
    }
}

In Chest.cpp you can see that I'm calling a method called get(something), this is what it looks like

std::vector<glm::vec3> getVertices() { return vertices; } // Note that vertices and etc. are vectors

So finally, is this not rendering or is it just not creating? I know the code is rather long, however I don't exactly know which parts are most important or most responsible for my problem.

genpfault
  • 51,148
  • 11
  • 85
  • 139

1 Answers1

1

I do not know if this will solve all issues in your code, but a few things I noticed:

  1. Mesh::storeDataInAttrubeList() does create a new VBO for each attribute array (which is possible in the GL, but you don't need that. Pack the whole object data into a single VBO). But what is worse is that it overwrites the (Mesh class attribute?) variable VBO. So you basically lose the IDs of the VBOs you created earlier. What is even worse is that you use VBO later as element array buffer when rendering, which will completely screw up your rendering.
  2. Mesh::renderVAO() changes the element buffer binding and resets it to 0 every time. This is not neccessary. The GL_ELEMENT_ARRAY_BUFFER binding is stored in the VAO, there is no need to set and reset it for every draw-call.
  3. Your OBJ parser does not properly duplicate vertices. You need to create a vertex for each unique vertexID/texcoordID/normalID triplet. But what you do is to just overwrite the vertex data at vertexID with the new texcoords and normals, instead of creating a completely new vertex.
derhass
  • 43,833
  • 2
  • 57
  • 78
  • If the vao's and vbo's were a vector, would that fix the overwriting? If so, for code like this glBindVertexArray(VAO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO); Would I have to make a for loop and every loop a new vao/vbo? – WolfAbyssWalker Jan 25 '20 at 16:01
  • "If the vao's and vbo's were a vector, would that fix the overwriting?" No. Why would it. Your algorithm is just wrong. " If so, for code like this glBindVertexArray(VAO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO); Would I have to make a for loop and every loop a new vao/vbo?" I have no idea what you even talk about here. If you have a single OBJ file, you would most likely create a single element index buffer, so no loops required. – derhass Jan 25 '20 at 16:35