0

I'm having some trouble drawing some 3d objects. I wrote a very simple .obj parser and I know it doesn't support all types of .obj files but that's fine and for me it doesn't need to. I just need to get something rendering on the screen correctly. Here's my parser

Mesh * Loader::loadObj(char * path) {
    Mesh * objectMesh;

    vector<Vertex *> indexedVertices;
    vector<Vector3 * > normals;
    vector<TextureCoordinate *> textureCoords;
    vector<vector<GLushort>> meshIndices;

    vector<GLushort> verticesIndexed;
    vector<GLushort> texturesIndexed;
    vector<GLushort> normalsIndexed;

    ifstream inFile;

    inFile.open(path);

    if (!inFile.good())
    {
        cerr << "Can't open texture file " << path << endl;
        return false;
    }

    std::cout << "Loading .obj file" << endl;

    while (!inFile.eof()) {
        string line;
        std::getline(inFile, line);

        if (line.substr(0, 2) == "v ") {//vertices
            std::vector<string> elements = Operations::split(Operations::trim(line.substr(3)), *" ");
            if (elements.size() > 1) {
                std::cout << "Loading vertex data: " << line << endl;
                Vertex * v = new Vertex(std::atof(elements.at(0).c_str()), std::atof(elements.at(1).c_str()), std::atof(elements.at(2).c_str()));
                indexedVertices.push_back(v);
            }
        }
        else if (line.substr(0, 2) == "vn") {//normals
            std::vector<string> elements = Operations::split(Operations::trim(line.substr(3)), *" ");
            std::cout << "Loading normal data: " << line << endl;
            if (elements.size() > 1) {
                Vector3 * v = new Vector3(std::atof(elements.at(0).c_str()), std::atof(elements.at(1).c_str()), std::atof(elements.at(2).c_str()));
                normals.push_back(v);
            }
        }
        else if (line.substr(0, 2) == "vt") {//tex coords
            std::cout << "Loading texture data: " << line << endl;
            std::vector<string> elements = Operations::split(Operations::trim(line.substr(3)), *" ");
            if (elements.size() > 1) {
                TextureCoordinate * t = new TextureCoordinate(std::atof(elements.at(0).c_str()), std::atof(elements.at(1).c_str()));
                textureCoords.push_back(t);
            }
        }
        else if (line.substr(0, 2) == "f ") {//faces / indices
            std::cout << "Loading face data: " << line << endl;
            std::vector<string> elements = Operations::split(Operations::trim(line.substr(2)), *" ");
            if (elements.size() > 1) {
                for (int i = 0; i < elements.size(); i++) {
                    std::vector<string> e = Operations::split(Operations::trim(elements.at(i)), *"/");
                    if (e.size() > 1) {
                        verticesIndexed.push_back(std::atof(e.at(0).c_str()) - 1);
                        texturesIndexed.push_back(std::atof(e.at(1).c_str()) - 1);
                        normalsIndexed.push_back(std::atof(e.at(2).c_str()) - 1);
                    }
                }
            }
        }
    }

    meshIndices.push_back(verticesIndexed);
    meshIndices.push_back(texturesIndexed);
    meshIndices.push_back(normalsIndexed);


    std::cout << "Face count: " << verticesIndexed.size() << " " << texturesIndexed.size() << " " << normalsIndexed.size() << endl;

    objectMesh = new Mesh(indexedVertices, normals, textureCoords, meshIndices);

    std::cout << "Vertices count: " << indexedVertices.size() << endl;
    std::cout << "Normals count: " << normals.size() << endl;
    std::cout << "Textures count: " << textureCoords.size() << endl;


    inFile.close();

    return objectMesh;

}

I believe this parses the data correctly, as the ending size of the vectors match the data in the file i.e. it would print 4700 vertices for example, and there would be 4700 vertices in the file.

I'm drawing the object using the following

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);


for (int i = 0; i < mesh->meshIndices.size(); i++) {
    glVertexPointer(3, GL_FLOAT, 0, mesh->indexedVertices[0]);
    glNormalPointer(GL_FLOAT, 0, mesh->normals[0]);
    glTexCoordPointer(2, GL_FLOAT, 0, mesh->textureCoords[0]);

    glDrawElements(GL_TRIANGLES, mesh->meshIndices[i].size(), GL_UNSIGNED_SHORT, &mesh->meshIndices[i][0]);
}

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

enter image description here

enter image description here

Why aren't the objects drawing correctly?

Edit: This works, but it's extremely slow

glBegin(GL_TRIANGLES);
for (int i = 0; i < mesh->meshIndices[0].size(); i++)//36
{

        glTexCoord2fv(&mesh->textureCoords[mesh->meshIndices[1][i]]->u);
        glNormal3fv(&mesh->normals[mesh->meshIndices[2][i]]->x);
        glVertex3fv(&mesh->indexedVertices[mesh->meshIndices[0][i]]->x);

}
kbz
  • 984
  • 2
  • 12
  • 30

1 Answers1

0

The problem is with the way you use the indices. The related key parts of your code are where you store away the vertex, texture coordinate and normal indices in your mesh:

meshIndices.push_back(verticesIndexed);
meshIndices.push_back(texturesIndexed);
meshIndices.push_back(normalsIndexed);

and then where you use these indices to draw:

for (int i = 0; i < mesh->meshIndices.size(); i++) {
    glVertexPointer(3, GL_FLOAT, 0, mesh->indexedVertices[0]);
    glNormalPointer(GL_FLOAT, 0, mesh->normals[0]);
    glTexCoordPointer(2, GL_FLOAT, 0, mesh->textureCoords[0]);

    glDrawElements(GL_TRIANGLES, mesh->meshIndices[i].size(),
                   GL_UNSIGNED_SHORT, &mesh->meshIndices[i][0]);
}

What this is doing is draw the mesh 3 times. Once with the vertex indices, once with the texture coordinate indices, and once with the normal indices. This can't possibly end up well. You need to draw once, with the right combination of vertices, texture coordinates, and positions.

The difficulty is that the OBJ format uses separate indices for each vertex attribute (positions, texture coordinates, normals), while OpenGL only supports using a single set of indices for all the attributes.

There are two main approaches to get this working:

  1. Do not use indexing. This is done by applying the index lookup while parsing, and building new arrays that you can then use for rendering without indexing. Sketching out the key parts of your code using this:

    if (line.substr(0, 2) == "v ") {
        ...
        indexedVertices.push_back(v);
    } else if (line.substr(0, 2) == "vn") {
        ...
        indexedNormals.push_back(v);
    } else if (line.substr(0, 2) == "vt") {
        ...
        indexedTextureCoords.push_back(t);
    } else if (line.substr(0, 2) == "f ") {
        ...
        unindexedVertices.push_back(
            indexedVertices[std::atof(e.at(0).c_str()) - 1]);
        unindexedTextureCoords.push_back(
            indexedTextureCoords[std::atof(e.at(1).c_str()) - 1]);
        unindexedNormals.push_back(
            indexedNormals[std::atof(e.at(2).c_str()) - 1]);
    }
    

    Then you can use the unindexed attributes, and draw them without indices, using glDrawArrays().

    The downside is that this replicates shared vertices, so it will consume more memory, and be slower.

  2. You create an OpenGL vertex for each unique combination of indices for vertex, texture coordinate, and normal. My older answer here contains some pseudo-code showing how this can be done: OpenGL - Index buffers difficulties.

Community
  • 1
  • 1
Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • Okay thanks I'll try and look into it. However, I don't think it is the loading that's the problem? If you take a look at my OP I have actually got it drawing using a different method. – kbz Apr 19 '15 at 07:25
  • Well, what you edited into the answer now is basically the same as my proposed solution 1. You shouldn't generally edit the question to incorporate the answer. The goal of SO is to build a repository of questions and answers, and that doesn't work if the questions are edited to contain the solution from the answer. – Reto Koradi Apr 19 '15 at 14:20
  • I got told that the code that I edited in is much worse than using the vertex pointers and so on because it's looping a lot of times. I will remove it though. – kbz Apr 20 '15 at 07:34