Reputation: 584
In order to use modern openGl with tinyobjloader, I'm trying to change the viewer exemple. I just change the LoadObjAndConvert function, to add vertex array objects as i seen in this tutorial, and to no longer use the buffer object that contains all the data (position, indices, color, uv) because it seems that we can no longer use it with modern openGL.
Result look like I have bad vertex index, the model is only partly rendered, and if the model has only one mesh (the stanford bunny) it does not even show up.
The code is too long, but it is the same as the tinyobjloader viewer exemple, so I will only post functions that are different.
Here is the LoadObjAndConvert function modified (modified parts are between lines to help) :
static bool LoadObjAndConvert(float bmin[3], float bmax[3],
std::vector<DrawObject>* drawObjects,
std::vector<tinyobj::material_t>& materials,
std::map<std::string, GLuint>& textures,
const char* filename) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
timerutil tm;
tm.start();
std::string base_dir = GetBaseDir(filename);
if (base_dir.empty()) {
base_dir = ".";
}
#ifdef _WIN32
base_dir += "\\";
#else
base_dir += "/";
#endif
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename,
base_dir.c_str());
if (!err.empty()) {
std::cerr << err << std::endl;
}
tm.end();
if (!ret) {
std::cerr << "Failed to load " << filename << std::endl;
return false;
}
printf("Parsing time: %d [ms]\n", (int)tm.msec());
printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3);
printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3);
printf("# of texcoords = %d\n", (int)(attrib.texcoords.size()) / 2);
printf("# of materials = %d\n", (int)materials.size());
printf("# of shapes = %d\n", (int)shapes.size());
// Append `default` material
materials.push_back(tinyobj::material_t());
for (size_t i = 0; i < materials.size(); i++) {
printf("material[%d].diffuse_texname = %s\n", int(i),
materials[i].diffuse_texname.c_str());
}
// Load diffuse textures
{
for (size_t m = 0; m < materials.size(); m++) {
tinyobj::material_t* mp = &materials[m];
if (mp->diffuse_texname.length() > 0) {
// Only load the texture if it is not already loaded
if (textures.find(mp->diffuse_texname) == textures.end()) {
GLuint texture_id;
int w, h;
int comp;
std::string texture_filename = mp->diffuse_texname;
if (!FileExists(texture_filename)) {
// Append base dir.
texture_filename = base_dir + mp->diffuse_texname;
if (!FileExists(texture_filename)) {
std::cerr << "Unable to find file: " << mp->diffuse_texname
<< std::endl;
exit(1);
}
}
unsigned char* image =
stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default);
if (!image) {
std::cerr << "Unable to load texture: " << texture_filename
<< std::endl;
exit(1);
}
std::cout << "Loaded texture: " << texture_filename << ", w = " << w
<< ", h = " << h << ", comp = " << comp << std::endl;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (comp == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
GL_UNSIGNED_BYTE, image);
} else if (comp == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, image);
} else {
assert(0); // TODO
}
glBindTexture(GL_TEXTURE_2D, 0);
stbi_image_free(image);
textures.insert(std::make_pair(mp->diffuse_texname, texture_id));
}
}
}
}
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
{
for (size_t s = 0; s < shapes.size(); s++) {
/*-----------------------------------------------------------*/
DrawObject o;// I keep this object for later purpose, texture, etc
//std::vector<float> buffer; // pos(3float), normal(3float), color(3float)
//I replace "buffer" by arrays:
std::vector<GLfloat> mesh_vertex;
std::vector<GLfloat> mesh_normals;
std::vector<GLfloat> mesh_colors;
std::vector<GLfloat> mesh_textCoords;
std::vector<GLuint> mesh_indices;
/*fill index array*/
for (long i = 0; i < shapes[s].mesh.indices.size(); i++)
{
mesh_indices.push_back(shapes[s].mesh.indices[i].vertex_index);
}
/*-----------------------------------------------------------*/
// Check for smoothing group and compute smoothing normals
std::map<int, vec3> smoothVertexNormals;
if (hasSmoothingGroup(shapes[s]) > 0) {
std::cout << "Compute smoothingNormal for shape [" << s << "]" << std::endl;
computeSmoothingNormals(attrib, shapes[s], smoothVertexNormals);
}
for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) {
tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0];
tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1];
tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2];
int current_material_id = shapes[s].mesh.material_ids[f];
if ((current_material_id < 0) ||
(current_material_id >= static_cast<int>(materials.size()))) {
// Invaid material ID. Use default material.
current_material_id =
materials.size() -
1; // Default material is added to the last item in `materials`.
}
// if (current_material_id >= materials.size()) {
// std::cerr << "Invalid material index: " << current_material_id <<
// std::endl;
//}
//
float diffuse[3];
for (size_t i = 0; i < 3; i++) {
diffuse[i] = materials[current_material_id].diffuse[i];
}
float tc[3][2];
if (attrib.texcoords.size() > 0) {
if ((idx0.texcoord_index < 0) || (idx1.texcoord_index < 0) ||
(idx2.texcoord_index < 0)) {
// face does not contain valid uv index.
tc[0][0] = 0.0f;
tc[0][1] = 0.0f;
tc[1][0] = 0.0f;
tc[1][1] = 0.0f;
tc[2][0] = 0.0f;
tc[2][1] = 0.0f;
} else {
assert(attrib.texcoords.size() >
size_t(2 * idx0.texcoord_index + 1));
assert(attrib.texcoords.size() >
size_t(2 * idx1.texcoord_index + 1));
assert(attrib.texcoords.size() >
size_t(2 * idx2.texcoord_index + 1));
// Flip Y coord.
tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index];
tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1];
tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index];
tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1];
tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index];
tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1];
}
} else {
tc[0][0] = 0.0f;
tc[0][1] = 0.0f;
tc[1][0] = 0.0f;
tc[1][1] = 0.0f;
tc[2][0] = 0.0f;
tc[2][1] = 0.0f;
}
float v[3][3];
for (int k = 0; k < 3; k++) {
int f0 = idx0.vertex_index;
int f1 = idx1.vertex_index;
int f2 = idx2.vertex_index;
assert(f0 >= 0);
assert(f1 >= 0);
assert(f2 >= 0);
v[0][k] = attrib.vertices[3 * f0 + k];
v[1][k] = attrib.vertices[3 * f1 + k];
v[2][k] = attrib.vertices[3 * f2 + k];
bmin[k] = std::min(v[0][k], bmin[k]);
bmin[k] = std::min(v[1][k], bmin[k]);
bmin[k] = std::min(v[2][k], bmin[k]);
bmax[k] = std::max(v[0][k], bmax[k]);
bmax[k] = std::max(v[1][k], bmax[k]);
bmax[k] = std::max(v[2][k], bmax[k]);
}
float n[3][3];
{
bool invalid_normal_index = false;
if (attrib.normals.size() > 0) {
int nf0 = idx0.normal_index;
int nf1 = idx1.normal_index;
int nf2 = idx2.normal_index;
if ((nf0 < 0) || (nf1 < 0) || (nf2 < 0)) {
// normal index is missing from this face.
invalid_normal_index = true;
} else {
for (int k = 0; k < 3; k++) {
assert(size_t(3 * nf0 + k) < attrib.normals.size());
assert(size_t(3 * nf1 + k) < attrib.normals.size());
assert(size_t(3 * nf2 + k) < attrib.normals.size());
n[0][k] = attrib.normals[3 * nf0 + k];
n[1][k] = attrib.normals[3 * nf1 + k];
n[2][k] = attrib.normals[3 * nf2 + k];
}
}
} else {
invalid_normal_index = true;
}
if (invalid_normal_index && !smoothVertexNormals.empty()) {
// Use smoothing normals
int f0 = idx0.vertex_index;
int f1 = idx1.vertex_index;
int f2 = idx2.vertex_index;
if (f0 >= 0 && f1 >= 0 && f2 >= 0) {
n[0][0] = smoothVertexNormals[f0].v[0];
n[0][1] = smoothVertexNormals[f0].v[1];
n[0][2] = smoothVertexNormals[f0].v[2];
n[1][0] = smoothVertexNormals[f1].v[0];
n[1][1] = smoothVertexNormals[f1].v[1];
n[1][2] = smoothVertexNormals[f1].v[2];
n[2][0] = smoothVertexNormals[f2].v[0];
n[2][1] = smoothVertexNormals[f2].v[1];
n[2][2] = smoothVertexNormals[f2].v[2];
invalid_normal_index = false;
}
}
if (invalid_normal_index) {
// compute geometric normal
CalcNormal(n[0], v[0], v[1], v[2]);
n[1][0] = n[0][0];
n[1][1] = n[0][1];
n[1][2] = n[0][2];
n[2][0] = n[0][0];
n[2][1] = n[0][1];
n[2][2] = n[0][2];
}
}
for (int k = 0; k < 3; k++) {
/*-----------------------------------------------------------*/
// I leave old calls to "buffer" in comment for understanding
//buffer.push_back(v[k][0]);
//buffer.push_back(v[k][1]);
//buffer.push_back(v[k][2]);
mesh_vertex.push_back(v[k][0]);
mesh_vertex.push_back(v[k][1]);
mesh_vertex.push_back(v[k][2]);
//buffer.push_back(n[k][0]);
//buffer.push_back(n[k][1]);
//buffer.push_back(n[k][2]);
mesh_normals.push_back(n[k][0]);
mesh_normals.push_back(n[k][1]);
mesh_normals.push_back(n[k][2]);
// Combine normal and diffuse to get color.
float normal_factor = 0.2;
float diffuse_factor = 1 - normal_factor;
float c[3] = {n[k][0] * normal_factor + diffuse[0] * diffuse_factor,
n[k][1] * normal_factor + diffuse[1] * diffuse_factor,
n[k][2] * normal_factor + diffuse[2] * diffuse_factor};
float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
if (len2 > 0.0f) {
float len = sqrtf(len2);
c[0] /= len;
c[1] /= len;
c[2] /= len;
}
//buffer.push_back(c[0] * 0.5 + 0.5);
//buffer.push_back(c[1] * 0.5 + 0.5);
//buffer.push_back(c[2] * 0.5 + 0.5);
mesh_colors.push_back(c[0] * 0.5 + 0.5);
mesh_colors.push_back(c[1] * 0.5 + 0.5);
mesh_colors.push_back(c[2] * 0.5 + 0.5);
//buffer.push_back(tc[k][0]);
//buffer.push_back(tc[k][1]);
mesh_textCoords.push_back(tc[k][0]);
mesh_textCoords.push_back(tc[k][1]);
/*-----------------------------------------------------------*/
}
}
o.vb_id = 0;
o.numTriangles = 0;
// OpenGL viewer does not support texturing with per-face material.
if (shapes[s].mesh.material_ids.size() > 0 &&
shapes[s].mesh.material_ids.size() > s) {
o.material_id = shapes[s].mesh.material_ids[0]; // use the material ID
// of the first face.
} else {
o.material_id = materials.size() - 1; // = ID for default material.
}
printf("shape[%d] material_id %d\n", int(s), int(o.material_id));
/*-----------------------------------------------------------*/
/*if (buffer.size() > 0) {
glGenBuffers(1, &o.vb_id);
glBindBuffer(GL_ARRAY_BUFFER, o.vb_id);
glBufferData(GL_ARRAY_BUFFER, buffer.size() * sizeof(float),
&buffer.at(0), GL_STATIC_DRAW);
o.numTriangles = buffer.size() / (3 + 3 + 3 + 2) /
3; // 3:vtx, 3:normal, 3:col, 2:texcoord
printf("shape[%d] # of triangles = %d\n", static_cast<int>(s),
o.numTriangles);
}
drawObjects->push_back(o);*/
// Replace by :
GLuint positionVBO = 0;
GLuint texcoordVBO = 0;
GLuint normalVBO = 0;
GLuint indicesEBO = 0;
// Upload per-vertex positions
if (!mesh_vertex.empty())
{
glGenBuffers(1, &positionVBO);
glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
glBufferData(GL_ARRAY_BUFFER, mesh_vertex.size() * sizeof(GLfloat), &mesh_vertex[0], GL_STATIC_DRAW); // GL_DYNAMIC_DRAW ?
glBindBuffer(GL_ARRAY_BUFFER, 0);
positionVBO_array.push_back(positionVBO);
}
// Upload per-vertex texture coordinates
if (!mesh_textCoords.empty())
{
glGenBuffers(1, &texcoordVBO);
glBindBuffer(GL_ARRAY_BUFFER, texcoordVBO);
glBufferData(GL_ARRAY_BUFFER,
mesh_textCoords.size() * sizeof(float),
&mesh_textCoords[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Upload per-vertex normals
if (!mesh_normals.empty())
{
glGenBuffers(1, &normalVBO);
glBindBuffer(GL_ARRAY_BUFFER, normalVBO);
glBufferData(GL_ARRAY_BUFFER, mesh_normals.size() * sizeof(GLfloat), &mesh_normals[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
normalVBO_array.push_back(normalVBO);
}
// Upload the indices that form triangles
if (!shapes[0].mesh.indices.empty())
{
glGenBuffers(1, &indicesEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
shapes[s].mesh.indices.size() * sizeof(unsigned int),
shapes[s].mesh.indices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
indicesEBO_array.push_back(indicesEBO);
indicesEBOSize_array.push_back(shapes[s].mesh.indices.size());
}
// Hook up vertex/index buffers to a "vertex array object" (VAO)
// VAOs are the closest thing OpenGL has to a "mesh" object.
// VAOs feed data from buffers to the inputs of a vertex shader.
GLuint meshVAO;
vglGenVertexArrays(1, &meshVAO);
meshVAO_array.push_back(meshVAO);// I keep the ids in order to loop inside meshVAO_array in the draw function
// Attach position buffer as attribute 0
if (positionVBO != 0)
{
glBindVertexArray(meshVAO);
// Note: glVertexAttribPointer sets the current
// GL_ARRAY_BUFFER_BINDING as the source of data
// for this attribute.
// That's why we bind a GL_ARRAY_BUFFER before
// calling glVertexAttribPointer then
// unbind right after (to clean things up).
glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 3, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Enable the attribute (they are disabled by default
// -- this is very easy to forget!!)
glEnableVertexAttribArray(0);
glBindVertexArray(0);
}
// Attach texcoord buffer as attribute 1
if (texcoordVBO != 0)
{
glBindVertexArray(meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, texcoordVBO);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(float) * 2, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
// Attach normal buffer as attribute 2
if (normalVBO != 0)
{
glBindVertexArray(meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, normalVBO);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE,
sizeof(float) * 3, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(2);
glBindVertexArray(0);
}
if (indicesEBO != 0)
{
glBindVertexArray(meshVAO);
// Note: Calling glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// when a VAO is bound attaches the index buffer to the VAO.
// From an API design perspective, this is subtle.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesEBO);
glBindVertexArray(0);
}
/*-----------------------------------------------------------*/
}
}
printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]);
printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
return true;
}
(Sorry for this long code block)
And here is the while loop of the main function, the only difference with tinyobjloader is between the two lines:
unsigned int program = shaders::CreateShader("data/simple.vert", "data/simple.frag"); // just some really simples shaders
while (glfwWindowShouldClose(window) == GL_FALSE) {
glfwPollEvents();
glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// camera & rotate
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
GLfloat mat[4][4];
gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0],
up[1], up[2]);
build_rotmatrix(mat, curr_quat);
glMultMatrixf(&mat[0][0]);
// Fit to -1, 1
glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
// Centerize object.
glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]),
-0.5 * (bmax[2] + bmin[2]));
/*-----------------------------------------------------------*/
//Draw(gDrawObjects, materials, textures);
// Can now bind the vertex array object to
// the graphics pipeline, to render with it.
glUseProgram(program);
for (int s = 0; s < meshVAO_array.size(); s++)
{
glBindVertexArray(meshVAO_array[s]);
glDrawElements(GL_TRIANGLES, indicesEBOSize_array[s], GL_UNSIGNED_INT, 0);//mesh.IndexCount
glBindVertexArray(0);
}
glUseProgram(0);
// when done, unbind it from the graphics pipeline:
glBindVertexArray(0);
/*-----------------------------------------------------------*/
glfwSwapBuffers(window);
}
What am I doing wrong?
Upvotes: 1
Views: 5209
Reputation: 210877
In the nested loops you all the indices of shapes[].mesh.indices
are use to lokkup the attributes, which are stored in attrib.vertices
, attrib.normals
and attrib.texcoords
.
This attributes are prepared and linearized. They are stored in there idexed order to the linear arrays mesh_vertex
, mesh_normals
, mesh_colors
and mesh_textCoords
.
But the indices are directly copied from shapes[].mesh.indices
to mesh_indices
for (long i = 0; i < shapes[s].mesh.indices.size(); i++)
{
mesh_indices.push_back(shapes[s].mesh.indices[i].vertex_index);
}
The indices in mesh_indices
still refer to the vertex coordinates stored in attrib.vertices
but the have no meaning for the attributes in the new containers.
The original indices are not needed any more. The indices of the new attribute would be continuously ascending: [0, 1, 2, 3, 4, 5 ...]
It is sufficient to draw the array of generic vertex attribute data in its existing order:
// you have to know the number of attributes
// something like mesh_vertex.size() / 3;
GLsizei no_of_attributes = .... ;
glBindVertexArray(meshVAO_array[s]);
glDrawArrays(GL_TRIANGLES, 0, no_of_attributes);
glBindVertexArray(0);
Upvotes: 1