I've recently been programming an .obj model loader, integrated with C++. So far I've successfully managed to adapt data from a file, acquiring its four attribute types (v, vn, vt and f) in order to set up my vertices and indices for an IBO structure. The problem is, the vertex positions seem to cross over each other more than once, causing a cluster of vertices that scatter within the form of the model.
(Demonstration is shown in the image link below.)
Click me to view the current result!
I've been thoroughly debugging many many times and still can't pin point this problem. Bearing in mind that I'm setting each normal and texture coordinate value to zero (temporarily) for testing purposes. Also, culling is disabled so it simply can't be a normal orientated problem.
Anyhow, getting to the code - I first have a Vertex struct that stores all of the attribute data for positions, normals and tex-coords:
struct Vertex
{
CBfloat v1, v2, v3;
CBfloat vn1, vn2, vn3;
CBfloat vt1, vt2;
Vertex(vec3 _position, vec3 _normal, vec2 _texCoord)
{
v1 = _position[0];
v2 = _position[1];
v3 = _position[2];
vn1 = _normal[0];
vn2 = _normal[1];
vn3 = _normal[2];
vt1 = _texCoord[0];
vt2 = _texCoord[1];
}
};
I use these containers to collect all of the data needed to render an IBO:
std::vector <Vertex> _vertices;
std::vector <CBuint> _indices;
Then, inside my loadOBJ function I retrieve data from the .obj and assign it to four temporary vector lists that contains vertex positions, vertex normals and vertex texture coordinates:
// Assign vertex data
std::vector <vec3> _v;
std::vector <vec3> _vn;
std::vector <vec2> _vt;
std::vector <Face> _f;
for (unsigned int i = 0; i < line.size(); i++)
{
switch ((line[i])[0])
{
case '#':
continue;
case '\0':
continue;
case 'm':
char mtl_url[128];
sscanf_s((&line[i])[0].c_str(), "mtllib %s", mtl_url, sizeof(mtl_url));
_mtl_url = mtl_url;
_materialised = true;
break;
case 'v':
float x, y, z;
if ((line[i])[1] == 'n')
{
sscanf_s((&line[i])[0].c_str(), "vn %f %f %f", &x, &y, &z);
_vn.push_back(vec3(x, y, z));
}
if ((line[i])[1] == 't')
{
sscanf_s((&line[i])[0].c_str(), "vt %f %f", &x, &y);
_vt.push_back(vec2(x, y));
}
else if ((line[i])[1] == ' ')
{
sscanf_s((&line[i])[0].c_str(), "v %f %f %f", &x, &y, &z);
_v.push_back(vec3(x, y, z));
}
break;
case 'u':
char material_element[128];
sscanf_s((&line[i])[0].c_str(), "usemtl %s", &material_element, sizeof(material_element));
// Assign new material to element
if (_material_element.size() > 1)
_f[_f.size() - 1].mat_id = _material_element.size();
_material_element.push_back(material_element);
break;
case 'f':
CBuint v_i[3];
CBuint vn_i[3];
CBuint vt_i[3];
sscanf_s((&line[i])[0].c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", &v_i[0], &vt_i[0], &vn_i[0], &v_i[1], &vt_i[1], &vn_i[1], &v_i[2], &vt_i[2], &vn_i[2]);
// Faces (IGNORE!)
_f.push_back(Face(v_i[0], vt_i[0], vn_i[0], v_i[1], vt_i[1], vn_i[1], v_i[2], vt_i[2], vn_i[2]));
// Indices
_indices.push_back(v_i[0] - 1);
_indices.push_back(vt_i[0] - 1);
_indices.push_back(vn_i[0] - 1);
_indices.push_back(v_i[1] - 1);
_indices.push_back(vt_i[1] - 1);
_indices.push_back(vn_i[1] - 1);
_indices.push_back(v_i[2] - 1);
_indices.push_back(vt_i[2] - 1);
_indices.push_back(vn_i[2] - 1);
break;
}
}
Next, I assign the vertices in relation to the number of vertex positions (temporary method for testing purposes):
// Optimise vertices (TEMP!!)
for (CBuint i = 0; i < _v.size(); i++)
{
if (i <= _vn.size() - 1 && i <= _vt.size() - 1)
_vertices.push_back(Vertex(_v[i], _vn[i], _vt[i]));
else if (i > _vn.size() - 1 && i <= _vt.size() - 1)
_vertices.push_back(Vertex(_v[i], vec3(0.0f, 0.0f, 0.0f), _vt[i]));
else
_vertices.push_back(Vertex(_v[i], vec3(0.0f, 0.0f, 0.0f), vec2(0.0f, 0.0f)));
}
And finally, the buffers. I begin by defining "BUFFER_OFFSET" as:
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
Then set up everything else as follows:
glGenVertexArrays(1, &_vao);
glBindVertexArray(_vao);
// Generate
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
// Bind
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// Buffer
glBufferData(GL_ARRAY_BUFFER, _vertices.size() * sizeof(Vertex), &_vertices[0], GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size() * sizeof(CBuint), &_indices[0], GL_STATIC_DRAW);
// Set attribs
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(0));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(CBfloat) * 3));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(CBfloat) * 6));
// Enable attribs
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
// Clean up
glBindVertexArray(0);
Which then of course leads on to the rendering stage:
virtual void render()
{
glUniformMatrix4fv(_u_model, 1, GL_FALSE, _model);
_materials[0]->bind();
glBindVertexArray(_vao);
glDrawElements(GL_TRIANGLES, _indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
And just for curiosity sake, the cube.obj file to which I'm testing on contains the following source:
v -0.5000 -0.5000 0.5000
v -0.5000 -0.5000 -0.5000
v 0.5000 -0.5000 -0.5000
v 0.5000 -0.5000 0.5000
v -0.5000 0.5000 0.5000
v 0.5000 0.5000 0.5000
v 0.5000 0.5000 -0.5000
v -0.5000 0.5000 -0.5000
# 8 vertices
vn 0.0000 -1.0000 -0.0000
vn 0.0000 1.0000 -0.0000
vn 0.0000 0.0000 1.0000
vn 1.0000 0.0000 -0.0000
vn 0.0000 0.0000 -1.0000
vn -1.0000 0.0000 -0.0000
# 6 vertex normals
vt 1.0000 0.0000 0.0000
vt 1.0000 1.0000 0.0000
vt 0.0000 1.0000 0.0000
vt 0.0000 0.0000 0.0000
# 4 texture coords
g Box001
s 2
f 1/1/1 2/2/1 3/3/1
f 3/3/1 4/4/1 1/1/1
s 4
f 5/4/2 6/1/2 7/2/2
f 7/2/2 8/3/2 5/4/2
s 8
f 1/4/3 4/1/3 6/2/3
f 6/2/3 5/3/3 1/4/3
s 16
f 4/4/4 3/1/4 7/2/4
f 7/2/4 6/3/4 4/4/4
s 32
f 3/4/5 2/1/5 8/2/5
f 8/2/5 7/3/5 3/4/5
s 64
f 2/4/6 1/1/6 5/2/6
f 5/2/6 8/3/6 2/4/6
# 12 faces
If anyone has any ideas on what's causing this vertex / index rendering bug, I'd much appreciate your enlightenment.
Thank you, William.