带有 tinyobjloader 的 OpenGL 顶点数组对象

OpenGL vertex array objects with tinyobjloader

为了将现代 openGl 与 tinyobjloader 一起使用,我正在尝试更改 viewer exemple。 我只是更改了 LoadObjAndConvert 函数,添加我在 tutorial 中看到的顶点数组对象,并且不再使用包含所有数据(位置、索引、颜色、uv)的缓冲区对象,因为看起来我们不能再与现代 openGL 一起使用。

结果看起来我的顶点索引不好,模型只渲染了一部分,如果模型只有一个网格(斯坦福兔子),它甚至不会显示。

代码太长,但是和tinyobjloader viewer example一样,所以我只post个不同的函数

这里是修改的LoadObjAndConvert函数(修改的部分在行之间有帮助):

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

(抱歉这个长代码块)

这里是main函数的while循环,与tinyobjloader的唯一区别在于两行之间:

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

我做错了什么?

在嵌套循环中,shapes[].mesh.indices 的所有索引都用于查找存储在 attrib.verticesattrib.normalsattrib.texcoords 中的属性。 此属性已准备好并线性化。它们以线性数组 mesh_vertexmesh_normalsmesh_colorsmesh_textCoords.

的索引顺序存储

但是索引直接从shapes[].mesh.indices复制到mesh_indices

for (long i = 0; i < shapes[s].mesh.indices.size(); i++)
{
    mesh_indices.push_back(shapes[s].mesh.indices[i].vertex_index);
}

mesh_indices 中的索引仍然引用存储在 attrib.vertices 中的顶点坐标,但对新容器中的属性没有任何意义。 不再需要原始索引。新属性的索引将不断递增:[0, 1, 2, 3, 4, 5 ...]

按现有顺序绘制通用顶点属性数据数组就足够了:

// 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);