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;
}
}