Entalpi Entalpi - 1 month ago 22
C++ Question

Vertices duplication elimination in models

When importing a model in .obj format a lot of the polygons share vertices and thus consume memory in vain, what I want to do is remove the duplicates by only saving unique vertices.

Hashing of Vertex

/// Template specialization for hashing of a Vec3
namespace std {
template<typename T>
struct hash<Vec3<T>> {
void hash_combine(size_t &seed, const size_t &hash) const {
seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

size_t operator() (const Vec3<T> &vec) const {
auto hasher = hash<float>{};
auto hashed_x = hasher(vertex.position.x);
auto hashed_y = hasher(vertex.position.y);
auto hashed_z = hasher(vertex.position.z);
auto hashed_color_r = hasher(vertex.color.r);
auto hashed_color_g = hasher(vertex.color.g);
auto hashed_color_b = hasher(vertex.color.b);
auto hashed_color_a = hasher(vertex.color.a);
auto hashed_texcoord_x = hasher(vertex.texCoord.x);
auto hashed_texcoord_y = hasher(vertex.texCoord.y);
auto hashed_normal_x = hasher(vertex.normal.x);
auto hashed_normal_y = hasher(vertex.normal.y);
auto hashed_normal_z = hasher(vertex.normal.z);

size_t seed = 0;
hash_combine(seed, hashed_x);
hash_combine(seed, hashed_y);
hash_combine(seed, hashed_z);
hash_combine(seed, hashed_texcoord_x);
hash_combine(seed, hashed_texcoord_y);
hash_combine(seed, hashed_normal_x);
hash_combine(seed, hashed_normal_y);
hash_combine(seed, hashed_normal_z);
return seed;
}
};
}


Importing mesh with tinyobjcloader

Mesh Renderer::load_mesh_from_file(std::string filepath) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
auto success = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filepath.c_str());
if (!success) { SDL_Log("Failed loading mesh %s: %s", filepath.c_str(), err.c_str()); return Mesh(); }

std::unordered_map<Vertex<float>, size_t> unique_vertices{};
Mesh mesh{};
for (auto shape : shapes) { // Shapes
size_t index_offset = 0;
for (auto face : shape.mesh.num_face_vertices) { // Faces (polygon)
for (auto v = 0; v < face; v++) {
tinyobj::index_t idx = shape.mesh.indices[index_offset + v];
Vertex<float> vertex{};
float vx = attrib.vertices[3 * idx.vertex_index + 0];
float vy = attrib.vertices[3 * idx.vertex_index + 1];
float vz = attrib.vertices[3 * idx.vertex_index + 2];
vertex.position = {vx, vy, vz};

float tx = attrib.vertices[3 * idx.texcoord_index + 0];
float ty = attrib.vertices[3 * idx.texcoord_index + 1];
vertex.texCoord = {tx, ty};

float nx = attrib.normals[3 * idx.normal_index + 0];
float ny = attrib.normals[3 * idx.normal_index + 1];
float nz = attrib.normals[3 * idx.normal_index + 2];
vertex.normal = {nx, ny, nz};

// These two lines work just fine (includes all vertices)
// mesh.vertices.push_back(vertex);
// mesh.indices.push_back(mesh.indices.size());

// Check for unique vertices, models will contain duplicates
if (unique_vertices.count(vertex) == 0) {
unique_vertices[vertex] = mesh.indices.size();
mesh.vertices.push_back(vertex);
mesh.indices.push_back(mesh.indices.size());
} else {
mesh.indices.push_back(unique_vertices.at(vertex));
}
}
index_offset += face;
}
}

SDL_Log("Number of vertices: %lu for model %s", mesh.vertices.size(), filepath.c_str());
return mesh;
}


The first image is when all the vertices are included.

enter image description here

This one is when I am only using unique vertices.

enter image description here

Any ideas?

Answer

if (unique_vertices.count(vertex) == 0) {

unique_vertices[vertex] = mesh.vertices.size();

mesh.indices.push_back(mesh.vertices.size());

mesh.vertices.push_back(vertex);

}

Explanation: indices are "pointers" to vertex locations. For that you need to get index where you write vertex data and not index for index data.

Comments