some explanations about textures

hi all,
i think i need some texture explanation, i’m doing basic texture drawing but, my texture look stretched in some points that seem points of conjunction like in this screenshots:
this is what it looks like when textured:
[ATTACH=CONFIG]736[/ATTACH]

this is texture coordinates color representation
[ATTACH=CONFIG]737[/ATTACH]

the mesh is loaded from an OBJ file and i’m sure that the texture coordinates are correctly loaded into my OpenGL application, since i checked many times.
Any help is appreciated, thanks.

You need to duplicate the vertices along the “seam”, one set has s texture coordinate 0, the other has s texture coordinate 1.

Right now for a triangle that touches the seam on the left the s tex coords look like this:


0.9     0.0
*---------*
 \        |
    \     |
       \  |
          *
        0.0

That means within the space of the triangle the coordinates are interpolated from 0.9 down to 0, i.e. almost the whole texture is crammed into that one triangle.

What you want looks like this:


0.9     1.0
*---------*
 \        |
    \     |
       \  |
          *
        1.0

But of course the vertex with s tex coord 1.0 also participates in the first triangle on the right of the seam:


0.9     1.0        0.1
*---------*--------*
 \        |       /
    \     |    /
       \  | /
          *
        1.0

so it needs to be duplicated:


0.9     1.0 0.0      0.1
*---------* *--------*
 \        | |     /
    \     | |   /
       \  | | /
          * *
        1.0 0.0

ok, so if i have a buffer (filled with positions, normals, uv_coord) how can i find all the vertices that i need to duplicate? (no need the code but only understand how to find these vertices)

If this problem is really present in the OBJ I’d call this a modelling mistake (or perhaps the exporter is flaky) and for me the preferred way would be to fix this in the model. Are you positive your loader correctly deals with the fact that OBJ has independent indices for the vertex attributes, whereas OpenGL only has a single index?

Anyway, I think you’d have to find vertices that belong to triangles with ‘very different’ texture coordinates. Currently I can not think of a precise way to define what ‘very different’ means, it depends on how finely your model is tessellated. For the first step (finding the triangles that share a vertex) the fully general solution is to construct a half edge graph of your model. This is a data structure that can answer questions like: given a vertex what are the edges that share it and given an edge what faces share the edge - it is quite commonly used for mesh manipulation, so searching should turn up a good deal of information.

this is how i fill my VBO:


	GLint j = 0; // this is the index counter so 0 = first index; 1 = second index and so on.

	for (GLint i = 0; i < model.vertex_data.size(); i += 8)
	{
		model.vertex_data[position_index[j] * 8] = vertex_position[position_index[j] * 3];
		model.vertex_data[position_index[j] * 8 + 1] = vertex_position[position_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 8 + 2] = vertex_position[position_index[j] * 3 + 2];
											  
		model.vertex_data[position_index[j] * 8 + 3] = normal[normal_index[j] * 3];
		model.vertex_data[position_index[j] * 8 + 4] = normal[normal_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 8 + 5] = normal[normal_index[j] * 3 + 2];

		model.vertex_data[position_index[j] * 8 + 6] = tex_coord[uv_index[j] * 2];
		model.vertex_data[position_index[j] * 8 + 7] = tex_coord[uv_index[j] * 2 + 1];
		
		j++;
	}

how can i manage it? (it works well except when i want to use textures)

Are you rendering this with glDrawArrays, i.e. without index? Why is model.vertex_data not filled linearly, i.e. why not:


for(int i = 0; i < numVertices; ++i)
{
    model.vertex_data[i * 8 + 0] = vertex_positions[position_index[j] * 3 + 0];
    model.vertex_data[i * 8 + 1] = vertex_positions[position_index[j] * 3 + 1];
    // ...
}

Such a buffer can be rendered without any indices, i.e. with glDrawArrays. The code you’ve posted makes me wonder if you are still using position_index as indices for glDrawElements, if that’s the case consider this situation in your OBJ:


// a face (L) on the left of the seam with indices
5/25/33    6/26/34    7/27/35

// a face (R) on the right of the seam
5/7/3    13/8/4    7/9/5

Note how they have the same position index, but different normals/tex coords. Now, if you apply your code it will write into model.vertex_data:


// when processing the first vertex of L
model.vertex_data[5 * 8 + 0,1,2] = vertex_position[5 * 3 + 0,1,2];
model.vertex_data[5 * 8 + 0,1,2] = normal[7 * 3 + 0,1,2];
model.vertex_data[5 * 8 + 0,1,2] = tex_coord[33 * 2 + 0,1];

// later when processing the first vertex of R
model.vertex_data[5 * 8 + 0,1,2] = vertex_position[5 * 3 + 0,1,2];
model.vertex_data[5 * 8 + 0,1,2] = normal[25 * 3 + 0,1,2];
model.vertex_data[5 * 8 + 0,1,2] = tex_coord[3 * 2 + 0,1];

See how processing R clobbers the normal and tex coords of L?

yes, you got it, I use indices of positions!
So what i have to do is fill the buffer linearly then use a new set of indices for my own format right?
PS: thanks a lot, tomorrow i will try to modify my code, i’ll let you know if this works but it should. thanks again i passed many nights on that

The OBJ format has a list of vertex coordinates, a list of texture coordinates, a list of normals, and a list of faces where each vertex is defined as indices into each of the arrays, i.e. a vertex index, texture coordinate index and normal index. Thus, two vertices can have the same vertex index (meaning that they’re at the same position) but different texture coordinate indices.

When using glDrawElements, each vertex is defined as a single index, which is used to fetch the position, texture coordinates and normal. So the structure of the data is less flexible than that of an OBJ file.

In essence, the OBJ structure looks like


struct obj_data {
    float positions[num_positions][3];
    float texcoords[num_texcoords][2];
    float normals[num_normals][3];
    int faces[num_faces][verts_per_face][3];
} obj_data;

and would be accessed like


for (int i = 0; i < num_faces; i++) {
    for (int j = 0; j < verts_per_face; j++) {
        // vertex j of face i
        glTexCoord2fv(obj_data.texcoords[obj_data.faces[i][j][1]]);
        glNormal3fv(obj_data.normals[obj_data.faces[i][j][2]]);
        glVertex3fv(obj_data.positions[obj_data.faces[i][j][0]]);
    }
}

while the OpenGL structure looks like


struct gl_data {
    float positions[num_vertices][3];
    float texcoords[num_vertices][2];
    float normals[num_vertices][3];
    int faces[num_faces][verts_per_face];
} gl_data;

and would be accessed like


for (int i = 0; i < num_faces; i++) {
    for (int j = 0; j < verts_per_face; j++) {
        // vertex j of face i
        glTexCoord2fv(gl_data.texcoords[gl_data.faces[i][j]]);
        glNormal3fv(gl_data.normals[gl_data.faces[i][j]]);
        glVertex3fv(gl_data.positions[gl_data.faces[i][j]]);
    }
}

You can get around this problem by simply making all vertices distinct. E.g. for each triangle you define 3 vertices, each with a position, texture coordinates and normal, and copy the data from the lists in the OBJ file. But this enlarges the data unnecessarily (a six-fold increase is not unlikely for a typical triangle mesh). Any vertex not on a texture seam or a hard corner (where the normals will differ between sides) will have a single position, set of texture coordinates and normal regardless of the number of faces which reference it.

So a better approach is to only duplicate the vertices which need to be duplicated, i.e. those which lie either on a texture seam (where the vertex has different texture coordinates on different sides) or a hard corner (where the vertex has different normals on different sides).

The way to do this is to map each OBJ vertex (a triple of position, texture coordinates, normal) to an OpenGL vertex using a dictionary (aka associative array, e.g. std::map in C++) where the key is the triple of indices from the OBJ file and the value is the index of the OpenGL vertex.

Whenever you encounter a new OBJ vertex (one which isn’t already in the dictionary), allocate a new OpenGL vertex (i.e. an index into the position, texture coordinate and normal arrays), copy the data from the OBJ position, texture coordinate and normal lists to the corresponding OpenGL arrays, then store the index of the newly-allocated OpenGL vertex in the dictionary.

This ensures that repeated references to identical vertices (identical position, texture coordinates and normal) only use a single OpenGL vertex, while non-identical vertices (where at least one of the properties differ) are distinct.

Example (from memory, treat as pseudo-code):


std::unordered_map<std::tuple<int,int,int>, int> vertices;
int n_vertices = 0;

for (int i = 0; i < num_faces; i++) {
    for (int j = 0; j < verts_per_face; j++) {
        // vertex j of face i
	int pi = obj_data.faces[i][j][0]; // position index
	int ti = obj_data.faces[i][j][1]; // texture coordinate index
	int ni = obj_data.faces[i][j][2]; // normal index
	auto key = std::make_tuple(pi, ti, ni);
	auto index_it = vertices.find(key);
	int index;
	if (index_it == vertices.end()) {
	    index = n_vertices++;
	    memcpy(gl_data.positions[index], obj_data.positions[pi], 3 * sizeof(float));
	    memcpy(gl_data.texcoords[index], obj_data.texcoords[ti], 2 * sizeof(float));
	    memcpy(gl_data.normals  [index], obj_data.normals  [ni], 3 * sizeof(float));
	    vertices[key] = index;
	}
	else
	    index = index_it->second;
	gl_data.faces[i][j] = index;
    }
}

ok, so i should iterate into my indices and where the position index is the same but the normal index or the uv index is different, i should duplicate these vertices.

guys i’m spending a lot of time but i really can’t figure out, i understood that i have to duplicate those vertices that share same positions but different normals and texture coordinates, the thing i can’t understand is how to do that.

this is the struct of my ObjLoader header i use to load OBJ files:


struct ObjModel
	{
		struct Mesh
		{
			std::vector <GLfloat> positions;
			std::vector <GLfloat> normals;
			std::vector <GLfloat> tex_coords;

			std::vector <GLuint> v_index;
			std::vector <GLuint> vn_index;
			std::vector <GLuint> vt_index;

			GLfloat m_max[3];
			GLfloat m_min[3];

			GLboolean has_name;
			GLboolean has_uv;
			GLboolean has_normal;

			std::string name;
		};

		struct IndexOffset
		{
			GLuint positions;
			GLuint normals;
			GLuint tex_coords;
		};

		IndexOffset index_offset;

		std::vector <Mesh> mesh;
		GLint sub_mesh_counter;
		std::vector <ObjMaterial> mtl;
	};

then in my ContentManager header i do all the stuff to adapt OBJ file format to OpenGL


GLvoid ContentManager::importContent(const GLchar *file_path, const GLchar* file_name)
{
	std::string full_path = file_path;
	full_path.append(file_name);

	std::string line;
	std::ifstream file(full_path.c_str());

	if (file.fail())
		std::cerr << "Cannot open file " << file_path << " file is illegible or doesn' t exist." << std::endl;

	else
	{
		ObjModel obj;
		obj = loadObj(file_path, file_name);

		for (GLint j = 0; j < obj.mesh.size(); j++)
		{
			deleteModelData();
			addMesh();

			if (obj.mesh[j].has_normal)
			{
				if (obj.mesh[j].has_uv)
				{
					for (GLint i = 0; i < obj.mesh[j].v_index.size(); i++)
					{
						obj.mesh[j].v_index[i] -= 1;
						obj.mesh[j].vn_index[i] -= 1;
						obj.mesh[j].vt_index[i] -= 1;
					}

					model.vertex_data.resize(obj.mesh[j].v_index.size() * 8);
					fillBuffer(obj.mesh[j].positions, obj.mesh[j].v_index, obj.mesh[j].normals, obj.mesh[j].vn_index, obj.mesh[j].tex_coords, obj.mesh[j].vt_index);
				}

				else
				{
					for (GLint i = 0; i < obj.mesh[j].v_index.size(); i++)
					{
						obj.mesh[j].v_index[i] -= 1;
						obj.mesh[j].vn_index[i] -= 1;
					}

					model.vertex_data.resize(obj.mesh[j].v_index.size() * 6);
					fillBuffer(obj.mesh[j].positions, obj.mesh[j].v_index, obj.mesh[j].normals, obj.mesh[j].vn_index);
				}
			}

			else
			{
				if (model.has_uv)
				{
					// continue;
				}

				else
				{
					// continue;
				}
			}

			hasNormal(model_counter, obj.mesh[j].has_normal);
			hasUv(model_counter, obj.mesh[j].has_uv);

			addMaxVertex(model_counter, obj.mesh[j].m_max[0], obj.mesh[j].m_max[1], obj.mesh[j].m_max[2]);
			addMinVertex(model_counter, obj.mesh[j].m_min[0], obj.mesh[j].m_min[1], obj.mesh[j].m_min[2]);

			model_counter += 1;
		}

		for (GLint i = 0; i < obj.mtl.size(); i++)
		{
			loadTexture(file_path, obj.mtl[i].diffuse_texture_name.c_str());
		}
	}
}

here are the fillBuffer functions:


GLvoid ContentManager::fillBuffer(std::vector <GLfloat> vertex_data, std::vector <GLuint> position_index, std::vector <GLfloat> normal, std::vector <GLuint> normal_index)
{
	GLint j = 0;

	for (GLint i = 0; i < model.vertex_data.size(); i += 6)
	{
		model.vertex_data[position_index[j] * 6] = vertex_data[position_index[j] * 3];
		model.vertex_data[position_index[j] * 6 + 1] = vertex_data[position_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 6 + 2] = vertex_data[position_index[j] * 3 + 2];

		model.vertex_data[position_index[j] * 6 + 3] = normal[normal_index[j] * 3];
		model.vertex_data[position_index[j] * 6 + 4] = normal[normal_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 6 + 5] = normal[normal_index[j] * 3 + 2];

		j++;
	}

	for (GLint i = 0; i < model.vertex_data.size(); i++)
		addVertexData(model.vertex_data[i], model_counter);
	for (GLint i = 0; i < position_index.size(); i++)
		addIndexData(position_index[i], model_counter);
}

GLvoid ContentManager::fillBuffer(std::vector <GLfloat> vertex_position, std::vector <GLuint> position_index, std::vector <GLfloat> normal, std::vector <GLuint> normal_index, std::vector <GLfloat> tex_coord, std::vector <GLuint> uv_index)
{

// i want to solve the case: positions, uvs, normals before

	GLint j = 0;

	for (GLint i = 0; i < model.vertex_data.size(); i += 8)
	{
		model.vertex_data[position_index[j] * 8] = vertex_position[position_index[j] * 3];
		model.vertex_data[position_index[j] * 8 + 1] = vertex_position[position_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 8 + 2] = vertex_position[position_index[j] * 3 + 2];
											  
		model.vertex_data[position_index[j] * 8 + 3] = normal[normal_index[j] * 3];
		model.vertex_data[position_index[j] * 8 + 4] = normal[normal_index[j] * 3 + 1];
		model.vertex_data[position_index[j] * 8 + 5] = normal[normal_index[j] * 3 + 2];

		model.vertex_data[position_index[j] * 8 + 6] = tex_coord[uv_index[j] * 2];
		model.vertex_data[position_index[j] * 8 + 7] = tex_coord[uv_index[j] * 2 + 1];
		
		j++;
	}

	for (GLint i = 0; i < model.vertex_data.size(); i++)
		addVertexData(model.vertex_data[i], model_counter);
	for (GLint i = 0; i < position_index.size(); i++)
		addIndexData(position_index[i], model_counter); // 

}

it would be great solve this problem without upset all my ObjLoader header but only something of my ContentManager header

i’ m currently using glDrawElements not old opengl stuff

EDIT:
i made it, i just replaced my fillVertexBuffer functions with tihs:


GLvoid ContentManager::fillBuffer(std::vector <GLfloat> vertex_position, std::vector <GLuint> position_index, std::vector <GLfloat> normal, std::vector <GLuint> normal_index, std::vector <GLfloat> tex_coord, std::vector <GLuint> uv_index)
{
	GLint j = 0;

	model.index_data.resize(0);

	for (GLint i = 0; i < model.vertex_data.size(); i += 8)
	{
		model.vertex_data[i] = vertex_position[position_index[j] * 3];
		model.vertex_data[i + 1] = vertex_position[position_index[j] * 3 + 1];
		model.vertex_data[i + 2] = vertex_position[position_index[j] * 3 + 2];
											  
		model.vertex_data[i + 3] = normal[normal_index[j] * 3];
		model.vertex_data[i + 4] = normal[normal_index[j] * 3 + 1];
		model.vertex_data[i + 5] = normal[normal_index[j] * 3 + 2];

		model.vertex_data[i + 6] = tex_coord[uv_index[j] * 2];
		model.vertex_data[i + 7] = tex_coord[uv_index[j] * 2 + 1];

		model.index_data.push_back(j);
		
		j++;
	}

	for (GLint i = 0; i < model.vertex_data.size(); i++)
		addVertexData(model.vertex_data[i], model_counter);
	for (GLint i = 0; i < model.index_data.size(); i++)
		addIndexData(model.index_data[i], model_counter);

}

IOW, you’re just making every vertex unique whether it’s necessary or not, which will result in your data being several times larger than it needs to be (typical figures are 6-fold for triangle meshes or 4-fold for quad meshes).

what do you mean, i did not modify the size of the buffer but only how is filled