Search code examples
c++openglglm-mathindiceswavefront

Custom OpenGL OBJ model loader indices calculation not working


I followed the tutorial from this website to make a simple model loader.

All the coordinates get read well, but one thing it doesn't go over is how to calculate the indices but in one of the further tutorials found here called VBO indexing. Although this seems like a good way to do it, it calculates the indices wrong.

I made a cube in blender and exported as obj, I loaded it with an obj loader that I previously had made by a teacher from my university and the indices are read as 0,1,2,3,4,(and so on)... 35 for the cube. While with the code below the indices go 0,1,2,3,... 16,17,0,18,3,19,4,6,20,7,9,21,10,12,22,13,15,23,16.

This is because it finds similar vertex and pushes it back to indices, while actually for this cube there shouldn't be any similar indices.

Function IndexVBO Slow

void Model::indexVBO_slow(
    std::vector<glm::vec3>& in_vertices,
    std::vector<glm::vec2>& in_uvs,
    std::vector<glm::vec3>& in_normals,

    std::vector<unsigned short>& out_indices,
    std::vector<glm::vec3>& out_vertices,
    std::vector<glm::vec2>& out_uvs,
    std::vector<glm::vec3>& out_normals
) {
    // For each input vertex
    for (unsigned int i = 0; i < in_vertices.size(); i++) {

        // Try to find a similar vertex in out_XXXX
        unsigned short index;
        bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i], out_vertices, out_uvs, out_normals, index);

        if (found) { // A similar vertex is already in the VBO, use it instead !
            out_indices.push_back(index);
        }
        else { // If not, it needs to be added in the output data.
            out_vertices.push_back(in_vertices[i]);
            out_uvs.push_back(in_uvs[i]);
            out_normals.push_back(in_normals[i]);
            out_indices.push_back((unsigned short)out_vertices.size() - 1);
        }
    }
}

I pass in the vertices, uvs and normals read from the file inside a vector of vector3s and vector2. And the others called out_ go in as empty containers.

Function getSimilarVertexIndex

// Searches through all already-exported vertices
// for a similar one.
// Similar = same position + same UVs + same normal
bool Model::getSimilarVertexIndex(glm::vec3& in_vertex, glm::vec2& in_uv, glm::vec3& in_normal, std::vector<glm::vec3>& out_vertices, std::vector<glm::vec2>& out_uvs, std::vector<glm::vec3>& out_normals, unsigned short& result)
{
    // Lame linear search
    for (unsigned int i = 0; i < out_vertices.size(); i++)
    {
        if (
            is_near(in_vertex.x, out_vertices[i].x) &&
            is_near(in_vertex.y, out_vertices[i].y) &&
            is_near(in_vertex.z, out_vertices[i].z) &&
            is_near(in_uv.x, out_uvs[i].x) &&
            is_near(in_uv.y, out_uvs[i].y) &&
            is_near(in_normal.x, out_normals[i].x) &&
            is_near(in_normal.y, out_normals[i].y) &&
            is_near(in_normal.z, out_normals[i].z)
            )
        {
            result = i;
            return true;
        }
    }

    // No other vertex could be used instead.
    // Needs to be added to VBO
    return false;
}

Function is_near

bool Model::is_near(float v1, float v2) {
    return fabs(v1 - v2) < 0.01f;
}

Fill EBO

// Fill EBO with indices 
m_buffer->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); //gl bind buffer
m_buffer->FillBuffer(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW); //gl buffer data

I am 100% sure the reason this doesn't work is that I don't completely understand the logic between grabbing the data from the obj and converting it into OpenGL indices. I understand the part that it checks if some of the data is found already so it can reuse some of the indices data, but the not the details of the if statement. Perhaps it is the fact that I send it as an EBO and the tutorial is doing VBO indexing.

Parser Code plus VBO indexing

std::vector <glm::vec3> m_vertices, m_normals;
std::vector <glm::vec2> m_uvs;

std::vector <unsigned int> vertexIndices, uvIndices, normalIndices;

std::vector<unsigned short> out_indices;
std::vector<glm::vec3> out_vertices;
std::vector<glm::vec2> out_uvs;
std::vector<glm::vec3> out_normals;

std::vector<glm::vec3> temp_vertices, temp_normals;
std::vector<glm::vec2> temp_uvs;

    
std::string temp_text = "";
FILE* file = fopen(filepath.c_str(), "r");

// Open File

if (!file)
{
    TheDebug::Log("Impossible to openfile", ALERT);
    return false;
}

// Read file until the end
while (1)
{
    char lineHeader[128];
    // read the first word of the line
    int res = fscanf(file, "%s", lineHeader);
    
    if (res == EOF)
    {
        break;
    }
    
    // Parse line header
    int x = strcmp(lineHeader, "v");
    if (strcmp(lineHeader, "v") == 0) 
    {
        glm::vec3 vertex;
        fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
        temp_vertices.push_back(vertex);
    }
    // Scan for UVs
    else if (strcmp(lineHeader, "vt") == 0) 
    {
        glm::vec2 uv;
        glm::vec2 uv2;
        glm::vec2 uv3;
        fscanf(file, "%f %f %f %f\n", &uv.x, &uv.y, &uv3.x, &uv2.y);
        temp_uvs.push_back(uv);
    }
    // Scan for normals
    else if (strcmp(lineHeader, "vn") == 0) {
        glm::vec3 normal;
        fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z);
        temp_normals.push_back(normal);
    }
    // Scan for faces
    else if (strcmp(lineHeader, "f") == 0) 
    {

        std::string vertex1, vertex2, vertex3;
        unsigned int vertexIndex[4], uvIndex[4], normalIndex[4];
        int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2]);
        
        if (matches != 9)
        {
            printf("File can't be read : ( Try exporting with other options\n");
            fclose(file);
            return false;
        }


        // Triangulated
        if (matches == 9)
        {
            //Add 3 total vertices
            m_totalVertices += 3;

            vertexIndices.push_back(vertexIndex[0]);
            vertexIndices.push_back(vertexIndex[1]);
            vertexIndices.push_back(vertexIndex[2]);
            uvIndices.push_back(uvIndex[0]);
            uvIndices.push_back(uvIndex[1]);
            uvIndices.push_back(uvIndex[2]);
            normalIndices.push_back(normalIndex[0]);
            normalIndices.push_back(normalIndex[1]);
            normalIndices.push_back(normalIndex[2]);
        }
    }
}

// Go through each vertex of each triangle
for (unsigned int i = 0; i < vertexIndices.size(); i++)
{
    unsigned int vertexIndex = vertexIndices[i];
    unsigned int uvIndex = uvIndices[i];
    unsigned int normalIndex = normalIndices[i];

    glm::vec3 vertex = temp_vertices[vertexIndex - 1];
    glm::vec2 uv = temp_uvs[uvIndex - 1];
    glm::vec3 normal = temp_normals[normalIndex - 1];
    m_normals.push_back(normal);

    m_vertices.push_back(vertex);
    m_uvs.push_back(uv);
}

fclose(file);
unsigned short result;

std::vector<GLuint> indices;
std::vector<glm::vec3> indexed_vertices;
std::vector<glm::vec2> indexed_uvs;
std::vector<glm::vec3> indexed_normals;

indexVBO(m_vertices, m_uvs, m_normals, indices, indexed_vertices, indexed_uvs, indexed_normals);

// After I fill buffers with the these indices.

Picture of loaded Cube

Cube with wrong indices

Keep in mind that the problem is in calculating the indices, since using another model loader the indices are different and go from 0-35 simultaneously, while mine doesn't.


Solution

  • After a long 4 days of testing stuff I found out that I could instead convert the vectors of v3s to vectors of GLfloats in a for loop to add all of them, and add the indices for each i.

    std::vector<GLfloat> testv;
    std::vector<GLfloat> testu;
    std::vector<GLfloat> testn;
    for (size_t i = 0; i < m_vertices.size(); i++)
    {
        testv.push_back(m_vertices[i].x);
        testv.push_back(m_vertices[i].y);
        testv.push_back(m_vertices[i].z);
    
        testu.push_back(m_uvs[i].x);
        testu.push_back(m_uvs[i].y);
    
        testn.push_back(m_normals[i].x);
        testn.push_back(m_normals[i].y);
        testn.push_back(m_normals[i].z);
        testindices.push_back(i);
    }
    

    This seems to work on at least 3 triangulated models I used, don't know if it is the best way though.