.OBJ texture coordinates aren't mapped correctly

This is a cross-post from game-dev because it seems more general to graphical programming than game development.

I have written a simple .obj parser for my OpenGL game engine that reads vertices, uv’s and normals; but when I get to drawing the model in the texture isn’t mapped correctly.

I have tried

uv.y = 1.0f-uv.y

to get inverted Y axis coordinates as suggested by a few people but my textures are still skewed.

I know the texture and my rendering are correct because when I render a plane, with hand written texture coordinates and vertices using the same texture, it renders fine. I can render a textured cube by hand fine too.

I am using an index buffer to store vertex elements and I use the face elements provided by the obj to fill texture coordinate and normal buffers with the correct vectors.

What is wrong with my parser?

PARSER CODE:


Model::Model(const string &givenName, const string &filePath){
    name = givenName;

    std::ifstream in(MODEL_PATH+filePath, std::ios::in);
    if(!in){
        error("Model \"" + filePath + "\" does not exist");
        return;
    }


    ModelMeshIndex tempindex;

    vector<vec3> normals;
    vector<vec2> uvs;

    string line;
    while(getline(in, line)){
        if(line.substr(0,2) == "v "){
                    std::istringstream s(line.substr(2));
                    vec3 v(0.0f);
                    s >> v.x >> v.y >> v.z;
                    data.vertices.push_back(v);
        }
        else if(line.substr(0,2) == "vt"){
                    std::istringstream s(line.substr(2));
                    vec2 v(0.0f);
                    s >> v.x >> v.y;
                    //v.y = 1.0f-v.y;
                    uvs.push_back(v);
        }
        else if(line.substr(0,2) == "vn"){
                    std::istringstream s(line.substr(2));
                    vec3 v(0.0f);
                    s >> v.x >> v.y >> v.z;
                    normals.push_back(v);
        }
        else if(line.substr(0,2) == "f "){
                    std::istringstream s(line.substr(1));
                    char tmp; // For the slashes between elements
                    unsigned short a,b,c, d,e,f, g,h,i;
                    s >> a >> tmp >> d >> tmp >>g;
                    s >> b >> tmp >> e >> tmp >>h;
                    s >> c >> tmp >> f >> tmp >>i;
                    a--; b--; c--; d--; e--; f--; g--; h--; i--;
                    data.index.vertexElements.push_back(a); data.index.vertexElements.push_back(b); data.index.vertexElements.push_back(c);
                    data.index.uvElements.push_back(d);     data.index.uvElements.push_back(e);     data.index.uvElements.push_back(f);
                    data.index.normalElements.push_back(g); data.index.normalElements.push_back(h); data.index.normalElements.push_back(i);
        }
        else if(line[0] == '#') continue;
        else continue;
    }

    for(unsigned int i = 0; i < data.index.normalElements.size(); ++i)
        data.normals.push_back(normals[data.index.normalElements[i]]);

    for(unsigned int i = 0; i < data.index.uvElements.size(); ++i)
        data.uvs.push_back(uvs[data.index.uvElements[i]]);

    //data.uvs.push_back(vec2( 0.0f, 0.0f)); // bottom left
    //data.uvs.push_back(vec2( 0.0f, 1.0f)); // top left
    //data.uvs.push_back(vec2( 1.0f, 0.0f)); // bottom right
    //data.uvs.push_back(vec2( 1.0f, 1.0f)); // top right

    good = true;
}

:doh:

.OBJ is multi-indexed; different vertex attributes have their own index (i.e. there is an index for position, one for normals, …). OpenGL only supports a single index that is used for all vertex attributes. I suppose that is why you do this:


for(unsigned int i = 0; i < data.index.normalElements.size(); ++i)
    data.normals.push_back(normals[data.index.normalElements[i]]);
 
for(unsigned int i = 0; i < data.index.uvElements.size(); ++i)
    data.uvs.push_back(uvs[data.index.uvElements[i]]);

However, you are not doing this for positions (presumably because you are still using indexed drawing?), but for that to work you need to arrange for the normals/texcoords to be in an order that is “compatible” with the position index. The usual way to deal with the multiple indices in OBJ is to go through the indices and look at each tuple (position_index, normal_index, texcoord_index); if the tuple has not occurred previously (i.e. it’s a new unique combination) write the referenced position, normal, texcoord to the vertex data buffer(s), write the number of unique tuples so far to the index buffer and remember that this tuple is associated with this new index. If the tuple occurred before, look up what index it was associated with and write that to the index buffer.

Could you give me a pseudo code example ? I’m not really sure how you suggest I store the vertices :confused:


vertices.clear
next_vertex = 0;

for each face
  for each vertex in face
      key = fn(position_index,normal_index,texcoord_index)
      if vertices.find(key)
        new_index = vertex(key).index
      else
        create vertex from position/normal/texcorrd
        store new vertex in vertices with key and next_vertex
        new_index = next_vertex
        increment next_index
      end
      save new_index in index buffer
  loop

Please note this is simplified code. It assumes .obj only has triangles (the specs allow for polygon faces and often the .obj will be quads not triangles.

I’m having a little trouble understanding your example; what is key? and what is the function used to define it?[QUOTE=tonyo_au;1250629]


vertices.clear
next_vertex = 0;

for each face
  for each vertex in face
      key = fn(position_index,normal_index,texcoord_index)
      if vertices.find(key)
        new_index = vertex(key).index
      else
        create vertex from position/normal/texcorrd
        store new vertex in vertices with key and next_vertex
        new_index = next_vertex
        increment next_index
      end
      save new_index in index buffer
  loop

Please note this is simplified code. It assumes .obj only has triangles (the specs allow for polygon faces and often the .obj will be quads not triangles.[/QUOTE]

Do you know how to use std::map or any other hashing function?

I don’t know very much about them but I understand the basics. I have been meaning to do some practical work with them to get better with them. In your example do you suggest that I map unsigned integers to vertices and in the function iterate through the map to check for pairs?

Basically yes.

The key is the key for the hash. std::map has an insert with a key and a data structure pair and a retrieve with a find with key as input.

Here your data will be vertex and the position in the vertex buffer to put this vertex.

Once you have passed all the faces, you build the vertex buffer by iterating through the vertex list and copy the vertex into its correct position.