プログラミング 見習い

プログラミングの勉強の学習日記的なブログ

OpenGLを学ぼうと足掻く OBJファイルの表示

前回までは立方体の表示を頑張ってみましたが、今回からはOBJファイルから読み込んで表示するプログラムを作成してみようと思います。

OBJファイルを読み込む関数は以下のようになるらしいです。
参考: OpenGL Programming/Modern OpenGL Tutorial Load OBJ - Wikibooks, open books for an open world

void LoadObj(const char* filename,
             std::vector<glm::vec4> &vertices,
             std::vector<glm::vec3> &normals,
             std::vector<GLushort> &elements)
{
  std::ifstream in(filename, std::ios::in);
  if (!in)
  {
    std::cerr << filename << "を開けませんでした" << std::endl;
    std::exit(1);
  }

  std::string line;
  while (getline(in, line))
  {
    if (line.substr(0, 2) == "v ")
    {
      std::istringstream s(line.substr(2));
      glm::vec4 v;
      s >> v.x;
      s >> v.y;
      s >> v.z;
      v.w = 1.0f;
      vertices.push_back(v);
    }
    else if (line.substr(0, 2) == "f ")
    {
      std::istringstream s(line.substr(2));
      GLushort a, b, c;
      s >> a;
      s >> b;
      s >> c;
      a--;
      b--;
      c--;
      elements.push_back(a);
      elements.push_back(b);
      elements.push_back(c);
    }
  }

  normals.resize(vertices.size(), glm::vec3(0.0, 0.0, 0.0));
  for (int i = 0; i < elements.size(); i += 3)
  {
    GLushort ia = elements[i];
    GLushort ib = elements[i+1];
    GLushort ic = elements[i+2];
    glm::vec3 normal = glm::normalize(glm::cross(
        glm::vec3(vertices[ib]) - glm::vec3(vertices[ia]),
        glm::vec3(vertices[ic]) - glm::vec3(vertices[ia])));
    normals[ia] = normals[ib] = normals[ic] = normal;
  }
}

この関数には色々な制限があるらしいです。(複数のオブジェクトを読み込めないなど)
OBJファイルでは、"v "から始まる行には頂点の情報があり、"f "から始まる行はエレメン卜の情報が(index番号は1から始まる)入っていることさえ知っていれば、そのままですね。

最後に頂点ごとに外積を使って法線ベクトルを計算しています。
この法線ベクトルを求める処理は様々な方法があって、すごくなめらかになるようにする計算方法もあるみたいです。
まあパッと見で上の計算方法が良いものではないことは理解できると思います。

メッシュの情報を読み込むコードは以下のようになります。

  std::cout << "メッシュを読み込みます" << std::endl;
  std::vector<glm::vec4> meshVertices;
  std::vector<glm::vec3> meshNormals;
  std::vector<GLushort>  meshElements;
  LoadObj("monkey.obj", meshVertices, meshNormals, meshElements);

前回までのプログラムの中の立方体に関する部分を消して、以下のように書いていきます。

  GLuint vboMeshHandle;
  glGenBuffers(1, &vboMeshHandle);

  glBindBuffer(GL_ARRAY_BUFFER, vboMeshHandle);
  glBufferData(GL_ARRAY_BUFFER,
               meshVertices.size() * sizeof(meshVertices[0]),
               meshVertices.data(), GL_STATIC_DRAW);
  
  
  GLuint iboMeshHandle;
  glGenBuffers(1, &iboMeshHandle);
  
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboMeshHandle);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshElements.size() * sizeof(meshElements[0]),
               meshElements.data(), GL_STATIC_DRAW);
  
  glGenVertexArrays(1, &vaoHandle);
  glBindVertexArray(vaoHandle);
  
  glEnableVertexAttribArray(attributeVertexPosition);

  glBindBuffer(GL_ARRAY_BUFFER, vboMeshHandle);
  glVertexAttribPointer(attributeVertexPosition,
                        4,
                        GL_FLOAT,
                        GL_FALSE,
                        0,
                        0
                        );
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboMeshHandle);

特に言うことは何もありません。

頂点シェーダを以下のように書き換えます。

#version 120
attribute vec3 VertexPosition;
uniform mat4 MVP;
void main() {
  gl_Position = MVP * vec4(VertexPosition, 1.0);
}

フラグメントシェーダも以下のように書き換えます。

#version 120
void main() {
  gl_FragColor = vec4(0.1, 0.2, 0.3, 1.0);
}

で、最後に描画関数内の描画処理部分を以下のように書き換えます。

  int size;
  glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
  glDrawElements(GL_TRIANGLES,
                 size/sizeof(GLushort),
                 GL_UNSIGNED_SHORT,
                 0);

これで、OBJファイルが表示されるようになりました。

プログラム全体としては以下のようになりました。

#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <cstdlib>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

GLuint vaoHandle;
GLuint programHandle;
GLfloat angle = 0.0;

void InitializeGL();
void DisplayGL();
void TimerGL(int);
void LoadObj(const char* filename,
             std::vector<glm::vec4> &vertices,
             std::vector<glm::vec3> &normals,
             std::vector<GLushort> &elements);

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitWindowSize(500, 500);
  glutInitWindowPosition(10, 10);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);
  glutCreateWindow("OpenGL test01");

  InitializeGL();

  glutDisplayFunc(DisplayGL);
  glutTimerFunc(16, TimerGL, 0);
  glutMainLoop();
}

void InitializeGL()
{
  std::cout << "初期化処理を行います" << std::endl;
  glEnable(GL_DEPTH_TEST);

  std::cout << "メッシュを読み込みます" << std::endl;
  std::vector<glm::vec4> meshVertices;
  std::vector<glm::vec3> meshNormals;
  std::vector<GLushort>  meshElements;
  LoadObj("monkey.obj", meshVertices, meshNormals, meshElements);
  
  std::cout << "GLEWの初期化" << std::endl;
  GLenum glewStatus = glewInit();
  if (glewStatus != GLEW_OK)
  {
    std::cerr << "Error: " << glewGetErrorString(glewStatus) << std::endl;
    std::exit(1);
  }
 
  std::cout << "頂点シェーダの設定" << std::endl;
  
  GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
  if (0 == vertShader)
  {
    std::cerr << "頂点シェーダ作成エラー" << std::endl;
    std::exit(1);
  }
  const GLchar* vertShaderCode =
      "#version 120\n"
      "attribute vec3 VertexPosition;"
      "uniform mat4 MVP;"
      "void main() {"
      "gl_Position = MVP * vec4(VertexPosition, 1.0);"
      "}";
  glShaderSource(vertShader, 1, &vertShaderCode, NULL);
  glCompileShader(vertShader);

  GLint result;
  glGetShaderiv(vertShader, GL_COMPILE_STATUS, &result);
  if (GL_FALSE == result)
  {
    std::cerr << "頂点シェーダのコンパイルに失敗しました" << std::endl;
    std::exit(1);
  }

  std::cout << "フラグメントシェーダの設定" << std::endl;
  GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
  if (0 == fragShader)
  {
    std::cerr << "フラグメントシェーダ作成エラー" << std::endl;
    std::exit(1);
  }
  const GLchar* fragShaderCode =
      "#version 120\n"
      "void main() {"
      "gl_FragColor = vec4(0.1, 0.2, 0.3, 1.0);"
      "}";
  glShaderSource(fragShader, 1, &fragShaderCode, NULL);
  glCompileShader(fragShader);

  glGetShaderiv(fragShader, GL_COMPILE_STATUS, &result);
  if (GL_FALSE == result)
  {
    std::cerr << "フラグメントシェーダのコンパイルに失敗しました" << std::endl;
    std::exit(1);
  }

  std::cout << "シェーダプログラムの設定" << std::endl;
  programHandle = glCreateProgram();
  if (0 == programHandle)
  {
    std::cerr << "プログラムオブジェクトの作成でエラーがありました" << std::endl;
    std::exit(1);
  }
  glAttachShader(programHandle, vertShader);
  glAttachShader(programHandle, fragShader);

  glLinkProgram(programHandle);

  GLint status;
  glGetProgramiv(programHandle, GL_LINK_STATUS, &status);
  if (GL_FALSE == status)
  {
    std::cerr << "シェーダプログラムのリンクに失敗しました" << std::endl;
    std::exit(1);
  }

  glUseProgram(programHandle);

  GLint attributeVertexPosition;
  attributeVertexPosition =
      glGetAttribLocation(programHandle, "VertexPosition");

  GLuint vboMeshHandle;
  glGenBuffers(1, &vboMeshHandle);

  glBindBuffer(GL_ARRAY_BUFFER, vboMeshHandle);
  glBufferData(GL_ARRAY_BUFFER,
               meshVertices.size() * sizeof(meshVertices[0]),
               meshVertices.data(), GL_STATIC_DRAW);
  
  
  GLuint iboMeshHandle;
  glGenBuffers(1, &iboMeshHandle);
  
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboMeshHandle);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshElements.size() * sizeof(meshElements[0]),
               meshElements.data(), GL_STATIC_DRAW);
  
  glGenVertexArrays(1, &vaoHandle);
  glBindVertexArray(vaoHandle);
  
  glEnableVertexAttribArray(attributeVertexPosition);

  glBindBuffer(GL_ARRAY_BUFFER, vboMeshHandle);
  glVertexAttribPointer(attributeVertexPosition,
                        4,
                        GL_FLOAT,
                        GL_FALSE,
                        0,
                        0
                        );
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboMeshHandle);
}

void DisplayGL()
{
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  angle += 0.3;
  if (angle >= 360.0)
  {
    angle -= 360.0;
  }
  glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0f), angle,
                                         glm::vec3(0.0f, 1.0f, 1.0f));
  
  glm::mat4 lookAt = glm::lookAt(glm::vec3(0.0, 0.0, 10.0),
                                 glm::vec3(0.0, 0.0, 0.0),
                                 glm::vec3(0.0, 1.0, 0.0));
  int screenWidth = glutGet(GLUT_WINDOW_WIDTH);
  int screenHeight = glutGet(GLUT_WINDOW_HEIGHT);
  glm::mat4 projection = glm::perspective(45.0f,
                                          static_cast<float>(screenWidth)/screenHeight,
                                          0.1f,
                                          100.0f);
  glm::mat4 mvp = projection * lookAt * rotationMatrix;

  GLuint location = glGetUniformLocation(programHandle, "MVP");
  glUniformMatrix4fv(location, 1, GL_FALSE, &mvp[0][0]);
  
  glBindVertexArray(vaoHandle);
  int size;
  glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
  glDrawElements(GL_TRIANGLES,
                 size/sizeof(GLushort),
                 GL_UNSIGNED_SHORT,
                 0);

  glutSwapBuffers();
}

void TimerGL(int)
{
  glutPostRedisplay();
  glutTimerFunc(16, TimerGL, 0);
}

void LoadObj(const char* filename,
             std::vector<glm::vec4> &vertices,
             std::vector<glm::vec3> &normals,
             std::vector<GLushort> &elements)
{
  std::ifstream in(filename, std::ios::in);
  if (!in)
  {
    std::cerr << filename << "を開けませんでした" << std::endl;
    std::exit(1);
  }

  std::string line;
  while (getline(in, line))
  {
    if (line.substr(0, 2) == "v ")
    {
      std::istringstream s(line.substr(2));
      glm::vec4 v;
      s >> v.x;
      s >> v.y;
      s >> v.z;
      v.w = 1.0f;
      vertices.push_back(v);
    }
    else if (line.substr(0, 2) == "f ")
    {
      std::istringstream s(line.substr(2));
      GLushort a, b, c;
      s >> a;
      s >> b;
      s >> c;
      a--;
      b--;
      c--;
      elements.push_back(a);
      elements.push_back(b);
      elements.push_back(c);
    }
  }

  normals.resize(vertices.size(), glm::vec3(0.0, 0.0, 0.0));
  for (int i = 0; i < elements.size(); i += 3)
  {
    GLushort ia = elements[i];
    GLushort ib = elements[i+1];
    GLushort ic = elements[i+2];
    glm::vec3 normal = glm::normalize(glm::cross(
        glm::vec3(vertices[ib]) - glm::vec3(vertices[ia]),
        glm::vec3(vertices[ic]) - glm::vec3(vertices[ia])));
    normals[ia] = normals[ib] = normals[ic] = normal;
  }
}

僕はメッシュファイルとしてBlenderのイメージキャラクターを使いました。
以下のように表示されました。
f:id:Code-C:20140313195700p:plain

猿っぽいなにかが回転しています。
光や材質の計算をしていないのでよくわからないですね。。。

ていうか、こんな書き換え書き換えみたいなバカバカしいことしてないでクラスか何かにまとめろよって話ですね。