Getting Texture Seams To Display Correctly

I feel like I’m missing something when trying to accomodate seams in my uv mapping. I’m using wavefront (.obj) files that are imported from blender and there doesn’t seem to be any way to export the marked seams I’ve created. Every explanation I can find online either tells people to double the vertices on the actual model (an unattractive option for me) or programmatically double the vertices after searching for faces with big uv jumps. Is this really the normal procedure? Since blender doesn’t export the edge data, it looks like people just build their own logic to find these vertices and then double them after loading the mesh. It seems like there would be a better method for such a common problem.

As a note, I would prefer a solution that doesn’t rely on opengl 3/4, because the machine I’m working with only has 2.1 capabilities for now.

Texture seams result in face vertices in the OBJ file which share the same vertex position index but have different texture coordinate indices. Similarly, sharp edges result in face vertices which share the same vertex position index but have different normal indices.

I was missing something! Thanks, that helps to clarify things. I didn’t realize the information was already implicitly there.

This was actually pretty tricky to figure out how to load only the non-repeated face elements, so I’ll post my solution to help anyone else trying to work through this problem. Using index buffers really does cut down on the number of vertices even after all of the doubling for seams and sharps.


bool load_mesh(
	const std::string& path,
	std::vector<float>& vertices,
	std::vector<float>& uvs,
	std::vector<float>& normals,
	std::vector<int>& indices,
	size_t& vertex_count,
        size_t& index_count)
{
	std::vector<vec3> vertex_positions;
	std::vector<vec2> vertex_uvs;
	std::vector<vec3> vertex_normals;
	std::vector<std::vector<std::vector<int> > > faces;

	std::ifstream in_file(path);

	if (!in_file)
		return false;

	for (std::string line; std::getline(in_file, line); )
	{
		std::stringstream line_stream(line);

		for (std::string token; line_stream >> token; )
		{
			if (token == "#") break;
			else if (token == "mtllib") break;
			else if (token == "usemtl") break;
			else if (token == "o") break;
			else if (token == "s") break;
			else if (token == "v")
			{
				float x, y, z;
				line_stream >> x >> y >> z;

				vertex_positions.push_back(vec3(x, y, z));
			}
			else if (token == "vt")
			{
				float u, v;
				line_stream >> u >> v;

				vertex_uvs.push_back(vec2(u, v));
			}
			else if (token == "vn")
			{
				float x, y, z;
				line_stream >> x >> y >> z;

				vertex_normals.push_back(vec3(x, y, z));
			}
			else if (token == "f")
			{
				std::string s1, s2, s3;
				line_stream >> s1 >> s2 >> s3;

				std::istringstream ss1(s1), ss2(s2), ss3(s3);

				std::vector<int> v1, v2, v3;

				while (std::getline(ss1, token, '/'))
					v1.push_back(std::stoi(token));

				while (std::getline(ss2, token, '/'))
					v2.push_back(std::stoi(token));

				while (std::getline(ss3, token, '/'))
					v3.push_back(std::stoi(token));

				std::vector<std::vector<int> > face;

				face.push_back(v1);
				face.push_back(v2);
				face.push_back(v3);

				faces.push_back(face);
			}
		}
	}

	std::vector<vec3> sorted_vertices;
	std::vector<vec2> sorted_uvs;
	std::vector<vec3> sorted_normals;
	std::vector<std::vector<int> > existing_elements;

	int index = 0;
	for (auto face : faces)
	{
		for (auto element : face)
		{
			std::vector<std::vector<int> >::iterator it;

			it = std::find(existing_elements.begin(), existing_elements.end(), element);

			if (it == existing_elements.end())
			{
				existing_elements.push_back(element);

				indices.push_back(index++);
				sorted_vertices.push_back(vertex_positions[element[0] - 1]);
				sorted_uvs.push_back(vertex_uvs[element[1] - 1]);
				sorted_normals.push_back(vertex_normals[element[2] - 1]);
			}
			else
			{
				indices.push_back(it - existing_elements.begin());
			}
		}
	}

	vertex_count = 3 * sorted_vertices.size();
	index_count = indices.size();

	vertices.resize(vertex_count);
	uvs.resize(2 * sorted_uvs.size());
	normals.resize(3 * sorted_normals.size());

	index = 0;
	for (auto vertex : sorted_vertices)
	{
		vertices[index++] = vertex.x;
		vertices[index++] = vertex.y;
		vertices[index++] = vertex.z;
	}

	index = 0;
	for (auto uv : sorted_uvs)
	{
		uvs[index++] = uv.x;
		uvs[index++] = uv.y;
	}

	index = 0;
	for (auto normal : sorted_normals)
	{
		normals[index++] = normal.x;
		normals[index++] = normal.y;
		normals[index++] = normal.z;
	}

	return true;
}

You might want to consider using std::unordered_map (or std::map if that’s not available) to hold the mapping between position/texcoord/normal triples and unique vertex indices. That has O(1) (or O(log(n)) for std::map) lookup rather than O(n) for using std::find().

Thanks, you’ve been very helpful. Is this the kind of thing you were talking about?


std::vector<vec3> sorted_vertices;
std::vector<vec2> sorted_uvs;
std::vector<vec3> sorted_normals;
std::unordered_map<std::vector<int>, int> unique_elements;

for (std::size_t i = 0, unique_index = 0; i < elements.size(); ++i)
{
        const auto& element = elements[i];
	const auto it = unique_elements.find(element);

	if (it == unique_elements.end())
	{
		unique_elements.insert(std::make_pair(element, unique_index));

		indices.push_back(unique_index);
		sorted_vertices.push_back(vertex_positions[element[0] - 1]);
		sorted_uvs.push_back(vertex_uvs[element[1] - 1]);
		sorted_normals.push_back(vertex_normals[element[2] - 1]);

                ++unique_index;
	}
	else
	{
		indices.push_back((*it).second);
	}
}

Yes, exactly.