プログラミング 見習い

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

OpenGLを学ぼうと足掻く 立方体の表示

今回から前回までに作った三角形が回転するプログラムを改造して、立方体が回転するプログラムを作ろうと思います。

まず頂点の情報が異なるのでそれを書き換えます。

  GLfloat positionData[] = {
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
     1.0, -1.0,  1.0,
    -1.0, -1.0,  1.0,
    -1.0,  1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0
  };
  GLfloat colorData[] = {
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
  };

例えばglDrawArraysの第一引数にGL_TRIANGLESを設定して描画を行っても正しく立方体は表示されません。
glDrawArraysは頂点番号を(0, 1, 2,  \cdot\cdot\cdot, 7)とすると、0, 1, 2で1つの三角形、3, 4, 5で1つの三角形を作るだけになってしまうと思います。
頂点の位置座標のデータの並びを工夫して、glDrawArraysの引数を適切なものにすればglDrawArraysで立方体を描画することもできると思いますが、一々そんなことを考えたくないです。

そこでIBO(index buffer object)というものを使うらしいです。
IBOの作成方法は同じバッファオブジェクト仲間なので、大体VBOの作成方法と一緒です。
以下のようにしてみました。

  GLuint iboCubeHandle;
  glGenBuffers(1, &iboCubeHandle);
  
  GLushort elementsData[] = {
    0, 1, 2, 3, // bottom
    4, 7, 6, 5, // up
    0, 3, 7, 4, // left
    1, 5, 6, 2, // right
    3, 2, 6, 7, // front
    1, 0, 4, 5  // back
  };

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboCubeHandle);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elementsData),
               elementsData, GL_STATIC_DRAW);

ただ、バッファをバインドするバインディングポイントにGL_ARRAY_BUFFERではなく、GL_ELEMENT_ARRAY_BUFFERを指定します。

VAOはVBOと頂点属性の接続を記憶するだけで、IBOのことは記憶してくれないです。
ですので、GL_ELEMENT_ARRAY_BUFFERバインディングポイントへのバインドは明示的に行わないとならないわけです。
そして、これはいまいちよく理解出来ていないのですが、IBOのバインドはVAOのバインドの後で行わなければならないみたいです。

これで描画関数の中でglDrawArraysを消して、以下のように書きます。

glDrawElements(GL_QUADS, 24, GL_UNSIGNED_SHORT, 0);

GLUTのDisplayModeの設定を以下のように書き換えます。

glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);

で、初期化関数の中で深度を有効にするために以下のコードを追加します。

glEnable(GL_DEPTH_TEST);

次に頂点シェーダを書き換えます。

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

まあ、RotationMatrixという名前をMVPという名前に変えてるだけですね。
前回は回転のアフィン変換のみをする行列を渡していたのでRotationMatrixという名前にしましたが、今回はモデル、ビュー、プロジェクションの変換を行う行列を渡すつもりなのでMVPという名前にしました。

次にDisplayGLを変更していこうと思います。

まずビュー変換行列を作成します。
GLMには今では非推奨とされているOpenGLの機能と似た機能をもつ関数が多数あるらしいです。(そういえば前回使ったglm::rotateもglRotatefと似た感じでしたね。)
gluLookAtっぽい関数としてglm::lookAtがあるのでそれを使うことにします。

今のままでは立方体と視点との距離があまりにも近いのでそれを離すように設定します。

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

ほんとにgluLookAtそっくりですね。

次に投影変換行列を作成します。
gluPerspectiveに似たGLMの関数glm::perspectiveを使用します。

gluPerspectiveと同じように、映すシーンのアスペクト比を第二引数に渡しますが、特別な理由がなければ、これはウィンドウの縦横の比にしておけばいいですね。
このサイズ得るにはglutGet関数を使えばいいですね。

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

こういうscreenのサイズとかはグローバルな変数にしてglutReshapeFuncに登録されているコールバック関数が呼び出される度にそこで設定するほうがいいのかなぁ。
よくわからないです。

あとはこれらの行列の積を求めてUniform変数MVPに送ればいいだけです。

  glm::mat4 mvp = projection * lookAt * rotationMatrix;

  GLuint location = glGetUniformLocation(programHandle, "MVP");
  glUniformMatrix4fv(location, 1, GL_FALSE, &mvp[0][0]);

以上で立方体が回転するプログラムが出来ました。

コードは以下のようになりました。

#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#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);

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 << "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;"
      "attribute vec3 VertexColor;"
      "uniform mat4 MVP;"
      "varying vec3 Color;"
      "void main() {"
      "Color = VertexColor;"
      "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"
      "varying vec3 Color;"
      "void main() {"
      "gl_FragColor = vec4(Color, 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;
  GLint attributeVertexColor;
  attributeVertexPosition =
      glGetAttribLocation(programHandle, "VertexPosition");
  attributeVertexColor =
      glGetAttribLocation(programHandle, "VertexColor");

  GLuint vboCubeHandles[2];
  glGenBuffers(2, vboCubeHandles);

  GLfloat positionData[] = {
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
     1.0, -1.0,  1.0,
    -1.0, -1.0,  1.0,
    -1.0,  1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0
  };
  GLfloat colorData[] = {
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
  };
  
  glBindBuffer(GL_ARRAY_BUFFER, vboCubeHandles[0]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(positionData),
               positionData, GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, vboCubeHandles[1]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(colorData),
               colorData, GL_STATIC_DRAW);

  GLuint iboCubeHandle;
  glGenBuffers(1, &iboCubeHandle);
  
  GLushort elementsData[] = {
    0, 1, 2, 3, // bottom
    4, 7, 6, 5, // up
    0, 3, 7, 4, // left
    1, 5, 6, 2, // right
    3, 2, 6, 7, // front
    1, 0, 4, 5  // back
  };

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboCubeHandle);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elementsData),
               elementsData, GL_STATIC_DRAW);
  
  glGenVertexArrays(1, &vaoHandle);
  glBindVertexArray(vaoHandle);
  
  glEnableVertexAttribArray(attributeVertexPosition);
  glEnableVertexAttribArray(attributeVertexColor);

  glBindBuffer(GL_ARRAY_BUFFER, vboCubeHandles[0]);
  glVertexAttribPointer(attributeVertexPosition, 3,
                        GL_FLOAT, GL_FALSE, 0, 0);

  glBindBuffer(GL_ARRAY_BUFFER, vboCubeHandles[1]);
  glVertexAttribPointer(attributeVertexColor, 3,
                        GL_FLOAT, GL_FALSE, 0, 0);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboCubeHandle);
}

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);
  glDrawElements(GL_QUADS, 24, GL_UNSIGNED_SHORT, 0);

  glutSwapBuffers();
}

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

どんどん長くなっていきますね。

実行結果は以下のようになりました。
f:id:Code-C:20140320215108p:plain