OBJ loader woos

Member
Posts: 31
Joined: 2009.01
Post: #1
Hey Everyone,

I've been working on an obj loader for sometime and can't seem to get texturing working correctly. It's partly because I don't quite understand it and can't find any "good" resources online.

I've tried a couple different approaches and neither seem to produce the correct results.

Recall many obj files have a list of vt that are a uv texture pair to a map. My naive approach was load all those into a big array, then in draw call

Code:
glTexCoordPointer(2, GL_FLOAT, 0, mTextureCoords);
...
DrawIndexedTriangle(3, GL_UNSIGNED_SHORT, &(group->mFaces[j]));

And just kind of hope everything would work with my index buffers, alas that seemed to not work.

My next approach I let each face manage their own set of uv coords in an array, such that I would enable that specific array before I pushed the verts to the card, i.e.

Code:
if(face->mFaceUVList)
                glTexCoordPointer(2, GL_FLOAT, 0, &(face->mFaceUVList[j]));
       DrawIndexedTriangle(3, GL_UNSIGNED_SHORT, &(group->mFaces[j]));

---
At this point I'm at a loss for what I need to do. I also have been reading about S & T versus UV (which I don't really undertand) as well as possible needing to duplicate verts at runtime?!

If anyone had any insight this would be great.

Thanks
Quote this message in a reply
Member
Posts: 269
Joined: 2005.04
Post: #2
haudio Wrote:At this point I'm at a loss for what I need to do. I also have been reading about S & T versus UV (which I don't really undertand)...

S and T are the same as U and V. Just different names for the same thing.

Quote:...as well as possible needing to duplicate verts at runtime?!

This. A single vertex can be used in multiple polygons, and that vertex can have different normals and texture coordinates depending on the poly. So there can be more texture coordinates than vertices if there's lots of UV seams or *less* if there's lots of texture mirroring and reuse going on.

Since vertex arrays have to have the same number of vertices/tex coords/normals, and they all have to use the same index you'll need to do some added work to make them all match up. An easy way is to load up all the data from the .obj file into their own arrays. Then create new arrays with size of numberOfFaces * 3 (we're assuming all triangles). Iterate through the face list copying the data to the new arrays. So something like this in pseudo-code:

Code:
numIndices = numFaces * 3;

vertices = (MySuperCoolVertexFormat *)malloc(numIndices * sizeof(MySuperCoolVertexFormat);
// etc for texCoords and normals

currentIndex = 0;
for (i = 0; i < numFaces; i++)
{
    vertices[currentIndex] = objVerts[objFaces[i].vert1];
    texCoords[currentIndex] = objTexCoords[objFaces[i].texCoord1];
    normals[currentIndex] = objNormals[objFaces[i].normal1];
    currentIndex++;

    vertices[currentIndex] = objVerts[objFaces[i].vert2];
    texCoords[currentIndex] = objTexCoords[objFaces[i].texCoord2];
    normals[currentIndex] = objNormals[objFaces[i].normal2];
    currentIndex++;

    vertices[currentIndex] = objVerts[objFaces[i].vert3];
    texCoords[currentIndex] = objTexCoords[objFaces[i].texCoord3];
    normals[currentIndex] = objNormals[objFaces[i].normal3];
    currentIndex++;
}

Then you can call glDrawArrays(GL_TRIANGLES, 0, numIndices); to draw the whole thing at once.
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #3
Thanks for the reply, however I want to continue using glDrawElements at least until I understand why my current approach does not work. I'll embellish with some additional code, as it seems similar to the approach you suggested. Note I've simplified the code for this post.

First some Data Structures
Code:
class CC_OBJGroup
{
   public:
        GLuint              mNumberOfFaces;
        CC_Face3D          *mFaces;    
        CC_TriangleUV           *mFaceUVList;
};
struct CC_Face3D
{
    uint16 v1;
    uint16 v2;
    uint16 v3;
};
struct CC_TriangleUV
{
    CC_TextureCoord v1uv;
    CC_TextureCoord v2uv;
    CC_TextureCoord v3uv;
};
struct CC_TextureCoord
{
    float32 u;
    float32 v;
};

In load here is how I load a face into my datastructure, imagine mTextureCoords is all the texturecoords in the file already loaded. Keep in mind the -1 is because obj starts at index 1 not 0.

Code:
groupFaceCount = 0;  
for(groupFaceCount = 0; groupFaceCount < all faces in file, groupFaceCount++)
//first vertex
NSString *oneGroup = [faceIndexGroups objectAtIndex:0];
NSArray *groupParts = [oneGroup componentsSeparatedByString:@"/"];
currentGroup->mFaces[groupFaceCount].v1 = [[groupParts objectAtIndex:0] intValue]-1;
currentGroup->mFaceUVList[groupFaceCount].v1uv.u = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].u;
currentGroup->mFaceUVList[groupFaceCount].v1uv.v = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].v;
            
            
//second vertex
oneGroup = [faceIndexGroups objectAtIndex:1];
groupParts = [oneGroup componentsSeparatedByString:@"/"];
currentGroup->mFaces[groupFaceCount].v2 = [[groupParts objectAtIndex:0] intValue]-1;
currentGroup->mFaceUVList[groupFaceCount].v2uv.u = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].u;
currentGroup->mFaceUVList[groupFaceCount].v2uv.v = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].v;
        
//third vertex
oneGroup = [faceIndexGroups objectAtIndex:2];
groupParts = [oneGroup componentsSeparatedByString:@"/"];
currentGroup->mFaces[groupFaceCount].v3 = [[groupParts objectAtIndex:0] intValue]-1;
currentGroup->mFaceUVList[groupFaceCount].v3uv.u = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].u;
currentGroup->mFaceUVList[groupFaceCount].v3uv.v = mTextureCoords[[[groupParts objectAtIndex:1] intValue]-1].v;



Lastly in draw

Code:
    for (uint32 i = 0; i < mGroupCount; ++i)
{
        CC_OBJGroup * group = &(mGroupList[i]);
    if (mTextureCoords && group->mMaterial->GetTexture())
        {
            group->mMaterial->GetTexture()->BindToGraphicsDevice();
        }

        
        if(group->mFaceUVList)
            glTexCoordPointer(2, GL_FLOAT, 0, (group->mFaceUVList));
       //note I've also tried
    //glTexCoordPointer(2, GL_FLOAT, 0, mTextureCoords);
    
        
        //draw indexed triangle is a wrapper over the open gl stuff
        DrawIndexedTriangle((group->mNumberOfFaces * 3), GL_UNSIGNED_SHORT, (group->mFaces));

}
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #4
Anyone have any ideas? I'm stumped!
Quote this message in a reply
Member
Posts: 715
Joined: 2003.04
Post: #5
I don't know enough C to know if this will help you or not but here it is anyway
iGame3D Source

Find "ig3d_ImportWavefrontOBJModel" at line 10788.

Good luck.
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #6
Hey thanks for the reply.

Yeah that's not too enlightning :/ I'm just trying to understand conceptually what I need to do...
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #7
igame3d Wrote:I don't know enough C to know if this will help you or not but here it is anyway
iGame3D Source

Find "ig3d_ImportWavefrontOBJModel" at line 10788.

Good luck.

MORE THAN 20KLOC OF UNINDENTED C.

Cry
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #8
haudio Wrote:Hey thanks for the reply.

Yeah that's not too enlightning :/ I'm just trying to understand conceptually what I need to do...

Right now, it's hard to tell if your loader is foobar, if the data structure is foobar, or if your render is.

So break it up into testable chunks. Rewrite your renderer using dirt simple immediate mode gl ( glBegin() -> glEnd() ). Create a tetrahedron in code using your datastructures, and render it.

When it renders, you know your data structure and immediate mode rendering are fine.

THen render a .obj file. If it renders correctly, you know your vertex array code was the culprit.

If the .obj doesn't render, you can assume your parser's the culprit.

.obj loading is pretty easy, but there's a lot of room for mistakes. I'd be happy to show you mine, but it's in C++. It is, however, pretty simple and cleanly implemented.
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #9
Hey TomorrowPlusX,

I've tried to break it down into manageable chunks. I've written mine in all C++ as well and that's where I'm most comfortable. My models load fine so I know it's not my vert array. The problem comes with the texture map, and that's where I'm having the biggest issues.

Does your loader support texture maps? Did you have any issues? Are there any hidden gotchas I should know about. If it does I'd love to take a look as I'm up against a wall right now...
Quote this message in a reply
Member
Posts: 715
Joined: 2003.04
Post: #10
Have you tried different sources of OBJs?

I know when we were making our various object loaders it seemed like every model of
the same format we came across had some different issue to deal with until we tweaked things to handle the variety.

Your .obj has a .mtl file?
Some apps export without this and you get no texture info.
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #11
Yeah my models use a .mtl file, I've tried a variety of models from max, and blender. It seems that I can get "better" results (although still wrong) from Blender, Max i have no idea wtf is going on. Plus every time I google the subject I get all these posts that contradict each other, i.e. flip flop the uv coordinates? So u becomes v and v becomes u? Or duplicate verticies. Or this or that.

I feel like my head is going to explode. And what's worse is I'll get to go through this again with a different format when I want to do animation...

Am I talking nonsense? I just don't understand what I'm missing...
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #12
haudio Wrote:Hey TomorrowPlusX,
Does your loader support texture maps? Did you have any issues? Are there any hidden gotchas I should know about. If it does I'd love to take a look as I'm up against a wall right now...

My obj loader does support texture maps. I haven't tested it thoroughly... though.

http://pastie.textmate.org/379336
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #13
As TomorrowPlusX said, obj loading is not hard. However, as igame3d pointed out, it can be very problematic depending on the file, so making a robust loader is difficult. I did lots of testing as I wrote mine, using many different obj files from many different sources. The reason you're probably seeing so much contradictory information all over the place is that some 3D packages do not follow the obj spec and basically output whatever they want and don't bother checking data integrity. You would be amazed at what kinds of weird mangled obj files I've seen. As a result, my loader is about ten times longer than TomorrowPlusX's (and mine isn't nearly as easy to read). I have to handle all kinds of special case scenarios, including tessellating n-sided and complex polygons. It's a pain to go through the trouble of trying to support as much as possible, so my suggestion is to only work with one obj export format from one 3D editor that you use the most (e.g. Blender, Wings3D, Cheetah3D, etc.). That way you have something that is at least consistently known to your loader and you won't have many surprises. When you stumble upon an obj that won't load (e.g. you download an obj from TurboSquid and your loader says it's garbage), just import it into your known editor and re-export it so your loader can understand it. There is very little consistency out there.

And lastly, my advice would be to start with a textured cube from your preferred 3D editor to start with.

Here are some links to obj spec info that I found useful for obj:

http://www.royriggs.com/obj.html
http://www.fileformat.info/format/wavefrontobj/
http://www.martinreddy.net/gfx/3d/OBJ.spec

and one for mtl:

http://www.fileformat.info/format/material/
Quote this message in a reply
Member
Posts: 31
Joined: 2009.01
Post: #14
I appreciate all the great responses! I got my loader working "sometimes." Some models work fine, others are screwed up. In general the bulk of the issues revolve around the texture map looking screwed up. On some objects the texture map looks correct, but on others it does not.

It seems .obj files exported from max don't work for me, while those with blender seem to work. Does max treat their texture coords different?

Some things I've tried bringing a max file into blender and re-exporting it seems NOT to work. Although bringing a file created in blender into max and re-export from max works.

At this point I'm at a loss. I have no clue what to do to even begin fixing this.

Any insight?
Quote this message in a reply
Member
Posts: 269
Joined: 2005.04
Post: #15
haudio Wrote:I appreciate all the great responses! I got my loader working "sometimes." Some models work fine, others are screwed up. In general the bulk of the issues revolve around the texture map looking screwed up. On some objects the texture map looks correct, but on others it does not.

It seems .obj files exported from max don't work for me, while those with blender seem to work. Does max treat their texture coords different?

Some things I've tried bringing a max file into blender and re-exporting it seems NOT to work. Although bringing a file created in blender into max and re-export from max works.

At this point I'm at a loss. I have no clue what to do to even begin fixing this.

Any insight?

Try inverting the V coordinate of your UVs. So instead of V, try -V. Or you can try flipping your texture as you load it in. Different programs use different coordinate schemes, so your texture might literally be upside-down.
Quote this message in a reply
Post Reply