プログラミング 見習い

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

OpenGLを学ぼうと足掻く 三角形を回転

今回は前回作成したプログラムを改造して、三角形が回転するようにします。

昔はアフィン変換をglScale*やglTranslate*やglRotate*を使って行っていたらしいですが、今は非推奨なので別の方法を使います。
前回の三角形を表示させるプログラムではアプリケーションプログラムからの入力にattribute変数を使用しましたが、こういったモデル変換や、ビュー変換、投影変換を行う行列をシェーダに渡すためには普通uniform変数を使用するらしいです。
uniform変数は頂点単位の属性に比べて変化が頻繁でないデータに使われることを意図していて、頂点単位の属性をユニフォーム変数で設定したりすることはできません。
モデルビュー変換行列や投影変換行列を頂点ごとに変更する必要がある場面ていうのは普通ないと思うので、こういった行列をシェーダに渡すためにuniform変数を使うというのは自然な考え方ですね。

では前回のプログラムの頂点シェーダを以下のように書き換えます。

#version 120

attribute vec3 VertexPosition;
attribute vec3 VertexColor;
uniform mat4 RotationMatrix;
varying vec3 Color;

void main() {
  Color = VertexColor;
  gl_Position = RotationMatrix * vec4(VertexPosition, 1.0);
}

特にいうことも無いですね。
回転のアフィン変換行列を(アプリケーションプログラムで計算して)RotationMatrixに渡せば、シェーダが個々の頂点の座標に対してそれを適用してくれるわけです。

こういった行列の計算はGLMというライブラリを使うといいみたいです。
GLSLと似た感じなのでとっつきやすいとかなんとか。
GLMもSynapticで探せばあったのでのままインストールしました。

目標は回転する三角形の表示、つまりはアニメーションなので、タイマーをつかいます。
ですのでmain関数内に以下の行を追加します。

glutTimerFunc(16, TimerGL, 0);

そして新しくTimerGL関数を実装します。

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

前回作成したプログラムではプログラムオブジェクトのハンドルはInitializeGLのローカル変数としましたが、これをグローバルにします。
さらに三角形の角度を示すグローバル変数angleを以下のように定義します。

GLfloat angle = 0.0;

次に描画関数DisplayGLを以下のようにします。

void DisplayGL()
{
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_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, 0.0f, 1.0f));

  GLuint location = glGetUniformLocation(programHandle,
                                         "RotationMatrix");
  if (location >= 0)
  {
    glUniformMatrix4fv(location, 1, GL_FALSE,
                       &rotationMatrix[0][0]);
  }
  
  glBindVertexArray(vaoHandle);
  glDrawArrays(GL_TRIANGLES, 0, 3);

  glutSwapBuffers();
}

TimerGLによってDisplayGLは定期的に(16msごと)に呼びだされることになりました。
その度にangleを0.3度増やします(360.0度を超えたら-360.0しています)。

次に回転行列を作成しています。
単位行列(glm::mat4(1.0f))をz軸(glm::vec3(0.0f, 0.0f, 1.0f))を中心にしてangleだけ回転させる行列に変換しています。

次にglGetUniformLocationを使用してシェーダプログラムの中のRotationMatrixの位置をlocationに格納して、glUniformMatrix4fvを使ってその場所に先ほど作成した回転行列を流しこんでいます。
あとは前回と同じように描画しているだけです。

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

#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;
  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 RotationMatrix;"
      "varying vec3 Color;"
      "void main() {"
      "Color = VertexColor;"
      "gl_Position = RotationMatrix * 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 vboTriangleHandles[2];
  glGenBuffers(2, vboTriangleHandles);

  GLfloat positionData[] = {
    -0.8f, -0.8f,  0.0f,
     0.8f, -0.8f,  0.0f,
     0.0f,  0.8f,  0.0f
  };
  GLfloat colorData[] = {
    1.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 1.0f
  };
  
  glBindBuffer(GL_ARRAY_BUFFER, vboTriangleHandles[0]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(positionData),
               positionData, GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, vboTriangleHandles[1]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(colorData),
               colorData, GL_STATIC_DRAW);
  
  glGenVertexArrays(1, &vaoHandle);
  glBindVertexArray(vaoHandle);
  
  glEnableVertexAttribArray(attributeVertexPosition);
  glEnableVertexAttribArray(attributeVertexColor);

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

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

void DisplayGL()
{
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_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, 0.0f, 1.0f));

  GLuint location = glGetUniformLocation(programHandle,
                                         "RotationMatrix");
  if (location >= 0)
  {
    glUniformMatrix4fv(location, 1, GL_FALSE,
                       &rotationMatrix[0][0]);
  }
  
  glBindVertexArray(vaoHandle);
  glDrawArrays(GL_TRIANGLES, 0, 3);

  glutSwapBuffers();
}

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

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