OBJ File Problem

Started by
10 comments, last by jeskeca 9 years, 4 months ago

Hey there,

So i've recently been trying to parse an OBJ file and I can parse it successfully as long as theres no normals or textures. Now I want to parse textures as well but as we all know the obj file face indices are different from the texture indices. For example f 1/2/3 should be f 1/1/1 in order to load it properly in opengl.

So I was wondering if someone could help me out on how to fix this problem here's my code, note it may be messy because i've been playing around with it trying to solve this problem:


int COS::LoadTexOBJ(string filename ,int objects)
{
	ifstream fobj;
	string line;
	string mtl;
	GLuint temp[3];
	char *fu;
	int size;

	fobj.open(filename, ios::in);

	while (!fobj.eof())
	{
		getline(fobj, line);

		if (line.find("v ") != line.npos)
		{
//parse vertices
			sscanf_s(line.c_str(), "v %f %f %f", &com[objects].Object.Triangle.xyz[0], &com[objects].Object.Triangle.xyz[1], &com[objects].Object.Triangle.xyz[2]);
			com[objects].Object.Triangle.vertices.push_back(com[objects].Object.Triangle.xyz[0]);
			com[objects].Object.Triangle.vertices.push_back(com[objects].Object.Triangle.xyz[1]);
			com[objects].Object.Triangle.vertices.push_back(com[objects].Object.Triangle.xyz[2]);
			com[objects].Object.Triangle.vertex_num += 1;

		}
		if (line.find("vt ") != line.npos)
		{
//parse texture coordinates
			sscanf_s(line.c_str(), "vt %f %f", &com[objects].Object.Triangle.Texture.uv[0], &com[objects].Object.Triangle.Texture.uv[1]);
			com[objects].Object.Triangle.Texture.tex.push_back(com[objects].Object.Triangle.Texture.uv[0]);
			com[objects].Object.Triangle.Texture.tex.push_back(com[objects].Object.Triangle.Texture.uv[1]);
			
			com[objects].Object.Triangle.Texture.tex_num += 1;
		}
		/*if (line.find("vt ") != line.npos)
		{
		sscanf_s(line.c_str(), "f %f %f ", &com[object].u, &com[object].v);
		}
		*/

	}

	fobj.close();
//close and re open it because we now have the number of texture coordinates
	fobj.open(filename, ios::in);

	while (!fobj.eof())
	{
		getline(fobj, line);
		if (line.find("f ") != line.npos)
		{
			sscanf_s(line.c_str(), "f %d/%d %d/%d %d/%d", &com[objects].Object.Triangle.points[0], &temp[0], &com[objects].Object.Triangle.points[1], &temp[1], &com[objects].Object.Triangle.points[2], &temp[2]);
			com[objects].Object.Triangle.points[0] -= 1;
			com[objects].Object.Triangle.points[1] -= 1;
			com[objects].Object.Triangle.points[2] -= 1;

			temp[0] -= 1;
			temp[1] -= 1;
			temp[2] -= 1;

			com[objects].Object.Triangle.faces.push_back(com[objects].Object.Triangle.points[0]);
			com[objects].Object.Triangle.faces.push_back(com[objects].Object.Triangle.points[1]);
			com[objects].Object.Triangle.faces.push_back(com[objects].Object.Triangle.points[2]);
//temp storage for the texture indices
			com[objects].Object.Triangle.Texture.temp.push_back(temp[0]);
			com[objects].Object.Triangle.Texture.temp.push_back(temp[1]);
			com[objects].Object.Triangle.Texture.temp.push_back(temp[2]);
			com[objects].Object.Triangle.face_num += 1;
		}
	}
	fobj.close();

//unrelated stuff just code for getting the texture path and buffer stuff
	mtl = filename.erase(filename.length() - 3, 3);
	mtl.append("mtl");
	
	
	fobj.open(mtl, ios::in);
	while (!fobj.eof())
	{
		getline(fobj, line);
		if (line.find("	map_Ka")!= line.npos)
		{
			
			com[objects].Object.Triangle.Texture.filename = line.substr(8, line.length());
			com[objects].Object.Triangle.Texture.texpath = com[objects].Object.Triangle.Texture.filename.c_str();
		
		}
	}

	fobj.close();

	com[object].Object.Triangle.Texture.TexID = LoadTGATexture((char*)com[objects].Object.Triangle.Texture.texpath);
	glGenTextures(1, &com[object].Object.Triangle.Texture.TexID);
	texture = true;

	com[objects].type = MESH;
	com[objects].Object.Triangle.draw = true;
	object = object + 1;

	GenerateColors(com[objects].Object.Triangle.rgb[0], com[objects].Object.Triangle.rgb[1], com[objects].Object.Triangle.rgb[2]);

	glGenBuffers(1, &com[objects].Object.Triangle.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, com[objects].Object.Triangle.vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*com[objects].Object.Triangle.vertices.size(), &com[objects].Object.Triangle.vertices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &com[objects].Object.Triangle.Texture.vto);
	glBindBuffer(GL_ARRAY_BUFFER, com[objects].Object.Triangle.Texture.vto);
	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*com[objects].Object.Triangle.Texture.tex.size(), &com[objects].Object.Triangle.Texture.tex[0], GL_STATIC_DRAW);

	glGenBuffers(1, &com[objects].Object.Triangle.vio);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, com[objects].Object.Triangle.vio);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*com[objects].Object.Triangle.faces.size(), &com[objects].Object.Triangle.faces[0], GL_STATIC_DRAW);

	glGenVertexArrays(1, &com[objects].Object.Triangle.vao);
	glBindVertexArray(com[objects].Object.Triangle.vao);
	return 0;
}
Advertisement

Snipper, try to invert your Texture coordinance (not its index). Assuming it does try and render it but its all out of whack.

I found that mine was all weird because of it. Also check if you have data for each thing before you try to put it in those buffers.

While I was coding my own OBJ way back I failed to export my model with the textures applied. Fully convinced I did, I though it was a code error and wasted some time trying to fix something not broken.

Snipper, try to invert your Texture coordinance (not its index). Assuming it does try and render it but its all out of whack.

I found that mine was all weird because of it. Also check if you have data for each thing before you try to put it in those buffers.

While I was coding my own OBJ way back I failed to export my model with the textures applied. Fully convinced I did, I though it was a code error and wasted some time trying to fix something not broken.

The textures do work and they do work when I render it the thing is since I dont know how to incorperate the uv indices the textures show up weird because of that. I need to sort the indices and texture coord somehow

OpenGL only supports 1 index buffer, where an index refers to the "complete" vertex that includes all attributes (uv, normal, etc).

The easiest way to go is to copy the position and uv coordinates to a buffer as you read (or after) the indices and just render using glDrawArrays without

an element array buffer. Otherwise, you'll need to build a new index buffer based on unique pairs of position+uv.

Also, the binding of GL_ELEMENT_ARRAY_BUFFER is part of the state of a vertex array. Even if you set up glVertexAttribPointers later, you might as well bind the VAO before the element buffer and have that all set (and not messing up the previous VAO's state).

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

OpenGL only supports 1 index buffer, where an index refers to the "complete" vertex that includes all attributes (uv, normal, etc).

The easiest way to go is to copy the position and uv coordinates to a buffer as you read (or after) the indices and just render using glDrawArrays without

an element array buffer. Otherwise, you'll need to build a new index buffer based on unique pairs of position+uv.

Also, the binding of GL_ELEMENT_ARRAY_BUFFER is part of the state of a vertex array. Even if you set up glVertexAttribPointers later, you might as well bind the VAO before the element buffer and have that all set (and not messing up the previous VAO's state).

hmm I sort of get what you're saying so essentially instead of using glDrawElements I should use glDrawArrays? How will the indices work then because if I don't use indices my vertices won't render properly, yea I know openGL uses one index buffer which is why i'm trying to find an alternative solution to loading the .obj file.

You can just loop over all indices from the obj and check if a vertex with the position and texture coordinate is already created. If not, then create a new vertex with the attributes and push its index to the list of indices.

Maybe something like this:


std::vector<Vertex> final_vertices;
std::vector<unsigned int> final_indices;

for(const Face &face : faces) {
    for(int i=0; i<3; ++i) {
        position = positions[face.position_index[i]]
        texcoord = texcoords[face.texcoord_index[i]]
        normal = normals[face.normal_index[i]]

        Vertex tmp_vertex(position, texcoord, normal)
	
        // Check if a vertex with the specified attributes is already generated
        if(tmp_vertex is already in vertices) {
            // Add the index of the already generated vertex
            final_indices.push_back(/*index of tmp_vertex in vertices */);
        } else {
            // Add the new vertex
            final_vertices.push_back(tmp_vertex);
            // The index of the new vertex is the last one in the vector
            final_indices.push_back(final_vertices.size() - 1);
        }
    }
}

Derp

If you need indices for your engine (or just want to always use indexed renders) Sponji has the right info.

What I was getting at with a flat array is that you can build a flat array from the indices after you've read the file:


std::vector<int> indices; // pairs: position, uv, position, uv, etc
std::vector<vertex> vertices(indices.size()/2);
for(int i = 0; i < indices.size()/2; ++i)
{
vertices[i].x = points[indices[i]].x; // and y, z
vertices[i].u = texcoords[indices[i+1]].u // and v
}
// where points and texcoords are the v and vt from the obj file
// and indices is all of the face data. and then 'vertices' is all you need into one vbo with glDrawArrays.

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

If you need indices for your engine (or just want to always use indexed renders) Sponji has the right info.

What I was getting at with a flat array is that you can build a flat array from the indices after you've read the file:


std::vector<int> indices; // pairs: position, uv, position, uv, etc
std::vector<vertex> vertices(indices.size()/2);
for(int i = 0; i < indices.size()/2; ++i)
{
vertices[i].x = points[indices[i]].x; // and y, z
vertices[i].u = texcoords[indices[i+1]].u // and v
}
// where points and texcoords are the v and vt from the obj file
// and indices is all of the face data. and then 'vertices' is all you need into one vbo with glDrawArrays.

You can just loop over all indices from the obj and check if a vertex with the position and texture coordinate is already created. If not, then create a new vertex with the attributes and push its index to the list of indices.

Maybe something like this:


std::vector<Vertex> final_vertices;
std::vector<unsigned int> final_indices;

for(const Face &face : faces) {
    for(int i=0; i<3; ++i) {
        position = positions[face.position_index[i]]
        texcoord = texcoords[face.texcoord_index[i]]
        normal = normals[face.normal_index[i]]

        Vertex tmp_vertex(position, texcoord, normal)
	
        // Check if a vertex with the specified attributes is already generated
        if(tmp_vertex is already in vertices) {
            // Add the index of the already generated vertex
            final_indices.push_back(/*index of tmp_vertex in vertices */);
        } else {
            // Add the new vertex
            final_vertices.push_back(tmp_vertex);
            // The index of the new vertex is the last one in the vector
            final_indices.push_back(final_vertices.size() - 1);
        }
    }
}

Now these are two great ideas and I think I have the correct code but I still see one error occuring. Here's the scenario.

Obj File:


# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 29.11.2014 03:08:41

mtllib tex.mtl

#
# object Box001
#

v  -6.4129 0.0000 -13.2553
v  -6.4129 0.0000 -23.2553
v  3.5871 0.0000 -23.2553
v  3.5871 0.0000 -13.2553
v  -6.4129 10.0000 -13.2553
v  3.5871 10.0000 -13.2553
v  3.5871 10.0000 -23.2553
v  -6.4129 10.0000 -23.2553
# 8 vertices

vt 0.3353 0.6607 0.0000
vt 0.3353 0.3332 0.0000
vt 0.6668 0.3332 0.0000
vt 0.6668 0.6607 0.0000
vt -0.0044 0.6687 0.0000
vt 0.3271 0.6687 0.0000
vt 0.3271 0.9962 0.0000
vt -0.0044 0.9962 0.0000
vt 0.3353 -0.0024 0.0000
vt 0.6668 -0.0024 0.0000
vt 0.6668 0.3251 0.0000
vt 0.3353 0.3251 0.0000
vt 0.6749 -0.0024 0.0000
vt 1.0065 -0.0024 0.0000
vt 1.0065 0.3251 0.0000
vt 0.6749 0.3251 0.0000
vt -0.0044 0.3332 0.0000
vt 0.3271 0.3332 0.0000
vt 0.3271 0.6607 0.0000
vt -0.0044 0.6607 0.0000
vt -0.0044 -0.0024 0.0000
vt 0.3271 -0.0024 0.0000
vt 0.3271 0.3251 0.0000
vt -0.0044 0.3251 0.0000
# 24 texture coords

g Box001
usemtl 01___Default
f 1/1 2/2 3/3 
f 3/3 4/4 1/1 
f 5/5 6/6 7/7 
f 7/7 8/8 5/5 
f 1/9 4/10 6/11 
f 6/11 5/12 1/9 
f 4/13 3/14 7/15 
f 7/15 6/16 4/13 
f 3/17 2/18 8/19 
f 8/19 7/20 3/17 
f 2/21 1/22 5/23 
f 5/23 8/24 2/21 
# 12 faces

Now you can see how some vertices use multiple tex coords which is weird and I don't understand. for example f 1/9 4/10 6/11, that face is using vertex six and tex coord 11 now if tex coord 11 is successfully put into the texcoord array as the 6th element we're good right? False two lines later this occurs, f 7/15 6/16 4/13. Now how could the 6th vertex also be using tex coord 16th. If 11 is already set the the 6th tex coord how would we now set 16 as the 6th element in the final array. Here's my pseudo code so far for what im going to do.


vector<GLfloat>indices; // final indices list (first part of f #/#
vector<GLuint>vertices; // final vertex list
vector<GLfloat>texcoords;//final tex coordinates
vector<GLfloat>temp_texcoord; // temporarily hold the vt (tex coords)
vector<GLfloat>temp_indices; //temporarily hold the texture indices only(second part of f #/#)

read all vts to temp_texcoord;
read first part of f #/# in indices;
read second part of f #/# in temp_indices;
read v in vertices;

for(int i =0;i<indices.size();i++)
{
	if(indices[i] != temp_indices[i]) // if the numbers arent the same ex. 5/11 instead of 5/5
	{
		//take the 5th element of the temp_coord which has the vt list and make it the 5th coordinate of the texcoords vector
		texcoords.push_back(temp_texcoord[temp_indices[i]]);
	}
	else if(indices[i] == temp_indices)
	{
		texcoords.push_back(temp_texcoord[temp_indices[i]]);
	}
}


but here's the problem this would work but i noticed a weird trend in the obj file.
f 6/11 5/12 1/9 now the 6th vertex is assigned to the 11 texture coordinate in the vt list but 2 lines after this appears:
f 7/15 6/16 4/13 now the 6th vertex is assigned to the 16 texture coordinate ??? how is that possible and now that ruins our code?
so how does that work and how could we bypass it because before we can draw to the screen the 11th array will not be the 6th object anymore the 16 will now be assigned to it.

For example, let's make position 6 be 0,1,0. The faces would then be: f (0,1,0)/11 5/12 1/9 then later on f 7/15, (0,1,0)/16 ... Two faces can and do touch after all. They aren't assigned, the data is just used in that order. If any part of the full vertex (position + uv) doesn't match another full vertex they are unique even if part is the same (and get different indices for the GL element buffer). You will end up duplicating data, outputing a position multiple times if it appears with different uvs.

Also, easier to think of those v-lines as positions.

Edit: if that's not terribly clear, I apologize. I'm exhausted. I'll post some code tomorrow if necessary.

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

For example, let's make position 6 be 0,1,0. The faces would then be: f (0,1,0)/11 5/12 1/9 then later on f 7/15, (0,1,0)/16 ... Two faces can and do touch after all. They aren't assigned, the data is just used in that order. If any part of the full vertex (position + uv) doesn't match another full vertex they are unique even if part is the same (and get different indices for the GL element buffer). You will end up duplicating data, outputing a position multiple times if it appears with different uvs.

Also, easier to think of those v-lines as positions.

hmm yea I forgot they were split up as a triangle so the same can be used more than once, but i'm still getting jumbled textures even with the fix I posted above?

This topic is closed to new replies.

Advertisement