CDR6275

Michio SHIRAISHI Official Site


OpenGL 4.1とOpenGL ES SL 1.0で学ぶ3次元コンピュータグラフィックス

東邦大学理学部情報科学科 白石路雄
最終更新: 2014年2月20日

2. 最初のプログラム

 この章では三角形をウィンドウに表示するプログラムを通して、基本的な部分について説明します。

2.4 ここまでのプログラムをきれいにする

 さて、これからいろいろとプログラムを拡張していきます。そのために、ここでプログラムをリファクタリングしておきたいと思います。

 クラス構成については、次のようにしました。

  1. SimpleWindowクラス:ウィンドウを表すクラス
  2. SimpleProgramObjectクラス:シェーダのコードをコンパイルしたりするクラス
  3. BaseApplicationクラス:これから書いていく様々なアプリケーションのベースとなるクラス
  4. FirstShaderRefactoredクラス:BaseApplicationクラスを拡張して使うメインとなるクラス

 そして、FirstShaderRefactoredMain.cppのようにMainという名前がついたファイルにプログラムの実行が始まるmain関数を書いておき、FirstShaderRefactoredクラスを呼び出すようにします。今後の解説では、SimpleWindowクラスとSimpleProgramObjectクラスとBaseApplicationクラスの3つを使い回していきます。そして、FirstShaderRefactoredクラスとFirstShaderRefactoredMain.cppに対応するクラスを作っていきます。

 それでは個々のファイルを見てみましょう。

FirstShaderRefatoredMain.cppファイル

#include "FirstShaderRefactored.h"

int main(void){
  FirstShaderRefactored app;
  // 初期化する
  if(!app.initialize()) return 1;
  // ループする
  while (app.isRunning()){
    // 描画のために情報を更新する
    app.update();
    // 描画する
    app.draw();
  }
  // 後始末する
  app.finalize();
  return 0;
}

 このファイルではプログラム全体の構造を規定しています。まず、FirstShaderRefactoredクラスのinitializeメソッドにより初期化した後、メインループに移ります。ループ内では、「updateメソッドにより描画のために情報を更新する」ことと「drawメソッドにより描画する」ことを繰り返し行います。そして、最後に後始末を行います。この構造は多くのインタラクティブなアプリケーションで共通する構造です。

 それぞれ、FirstShaderRefactotedクラスのメソッドを呼び出すことで実行されます。

FirstShaderRefatoredクラス

#ifndef _FirstShaderRefactored_Defined
#define _FirstShaderRefactored_Defined

#include "BaseApplication.h"

class FirstShaderRefactored : public BaseApplication{
private:
  GLuint vertexArrayObject;  // 頂点配列オブジェクト
  GLuint vertexBufferObject; // 頂点バッファオブジェクト
public:
  bool initialize();
  void draw();
};

#endif

 FirstShaderRefatoredクラスは、BaseApplicationクラスを拡張したクラスで、初期化のためのinitializeメソッドと描画のためのdrawメソッドのみを実装することにします。FirstShaderRefatoredMain.cppから呼び出されている他のメソッド(isRunningメソッドとupdateメソッドとfinalizeメソッド)は、BaseApplicationクラスのものをそのまま使用します。

#include <iostream>

#include "FirstShaderRefactored.h"
#include "SimpleProgramObject.h"

// 三角形の3つの頂点の座標
static float faceVertexCoordinates[] = {
  0.0f, 0.0f, 0.0f,
  1.0f, 0.0f, 0.0f,
  0.0f, 1.0f, 0.0f,
};
enum
{
  ATTRIBUTE_VERTEX_COORDINATE,
  NUM_ATTRIBUTES
};

bool FirstShaderRefactored::initialize(){
  BaseApplication::initializeWindow(640, 640, "FirstShaderRefactored");

  SimpleProgramObject programObject;
  const GLchar* vertexShaderFileName = "first.vs";
  const GLchar* fragmentShaderFileName = "first.fs";
  program = programObject.createProgram(vertexShaderFileName, fragmentShaderFileName, shaderSearchPath);

  // シェーダコード内の変数にインデックスを設定する
  glBindAttribLocation(program, ATTRIBUTE_VERTEX_COORDINATE, "vertexCoordinate");
  
  programObject.linkProgram();
  
  // 頂点配列オブジェクトを作成して設定する
#if TARGET_OS_IPHONE
  glGenVertexArraysOES(1, &vertexArrayObject);
  glBindVertexArrayOES(vertexArrayObject);
#else
  glGenVertexArrays(1, &vertexArrayObject);
  glBindVertexArray(vertexArrayObject);
#endif
  
  // 頂点バッファオブジェクトを作成する
  glGenBuffers(1, &vertexBufferObject);
  glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
  glBufferData(GL_ARRAY_BUFFER, sizeof(float)*9, faceVertexCoordinates, GL_STATIC_DRAW);
  
  // 頂点バッファオブジェクトにシェーダ内の変数vertexCoodrinateを結びつける
  glEnableVertexAttribArray(ATTRIBUTE_VERTEX_COORDINATE);
  glVertexAttribPointer(ATTRIBUTE_VERTEX_COORDINATE, 3, GL_FLOAT, GL_FALSE, sizeof(float)*3, 0);
  
  // 背景色の設定
  glClearColor(0.75f, 0.75f, 0.75f, 1.0f);
  
  return true;
}


void FirstShaderRefactored::draw(){
#if TARGET_OS_IPHONE
  int viewport[4];
  glGetIntegerv(GL_VIEWPORT, viewport);
  glViewport(0, (viewport[3]-viewport[2])/2, viewport[2], viewport[2]);
#endif
  // 背景のクリア
  glClear(GL_COLOR_BUFFER_BIT);
  // 使用するプログラムオブジェクトをする
  glUseProgram(program);
  // 三角形を描く
  glDrawArrays(GL_TRIANGLES, 0, 3);
  // 使用するプログラムオブジェクトを解除する
  glUseProgram(0);
#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
  // バッファの入れ替え
  glfwSwapBuffers(window);
  // イベントの取得
  glfwPollEvents();
#endif
}


 FirstShaderRefactored.cppファイルで実装を行います。基本的に前回までで書いたコードを切り出したものが入っています。initializeメソッドの先頭では、親クラスであるBaseApplicationクラスのinitializeメソッドを呼び出しています。その中でウィンドウを作成しています。そして、SimpleProgramObjectクラスにシェーダのファイル名を渡してコンパイルさせ、頂点シェーダ内のvertexCoordinate変数にインデックスを設定し、プログラムをリンクしています。そして、頂点配列オブジェクトの作成、頂点バッファオブジェクトの作成というように進みます。

そのほかのクラス

 それ以外のクラスに関しては、コードを載せるのにとどめておきます。

#ifndef _BaseApplication_Defined
#define _BaseApplication_Defined

#if _WIN32
#include <GL/glew.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glfw3dll.lib")
#pragma comment(lib, "opengl32.lib")
#endif

#if __APPLE__
#include <TargetConditionals.h>
#endif

#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
#if (TARGET_OS_MAC && !TARGET_OS_IPHONE)
#define GLFW_INCLUDE_GLCOREARB
#define GL3_PROTOTYPES
#endif
#include <GLFW/glfw3.h>
#include "SimpleWindow.h"
#endif

#if TARGET_OS_IPHONE
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#endif

class BaseApplication{
protected:
  int windowWidth;                // ウィンドウの幅
  int windowHeight;               // ウィンドウの高さ
  const GLchar* shaderSearchPath; // シェーダコードが格納されているディレクトリ
  const GLchar* modelSearchPath;  // モデルデータが格納されているディレクトリ
  GLuint program;                 // プログラムオブジェクト

  // GLFWのウィンドウ (Windows環境とMac環境)
#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
  GLFWwindow* window;
#endif


public:
  BaseApplication();                                   // コンストラクタ
  void setShaderSearchPath(const GLchar* shaderSearchPath);
  void setModelSearchPath(const GLchar* modelSearchPath);
  virtual bool initialize() = 0;
  bool initializeWindow(int windowWidth, int windowHeight, const char* windowTitle = nullptr);  // 初期化
  bool isRunning();                                    // ループ内でアプリケーションを動かし続けるかどうか返す
  void update();                                       // データのアップデート
  virtual void draw() = 0;                             // 描画するメソッド (純粋仮想関数)
  void finalize();                                     // アプリケーション終了時のリソースの解放
  // iOS環境でデバイスのサイズを指定するためのメソッド
#if TARGET_OS_IPHONE
  void setDeviceSize(float width, float height);
#endif
};

#endif
#include <iostream>

#include "BaseApplication.h"

BaseApplication::BaseApplication(){
#if _WIN32  // Windows環境では.vcxprojファイルがあるディレクトリがカレントディレクトリになる
  shaderSearchPath = "../../../src/shaders/";
  modelSearchPath  = "../../../models/MikuMikuDance_Model/";
#else
  shaderSearchPath = "";
  modelSearchPath  = "";
#endif
}

void BaseApplication::setShaderSearchPath(const GLchar* shaderSearchPath){
  this->shaderSearchPath = shaderSearchPath;
}

void BaseApplication::setModelSearchPath(const GLchar* modelSearchPath){
  this->modelSearchPath = modelSearchPath;
}


#if TARGET_OS_IPHONE
void BaseApplication::setDeviceSize(float width, float height){
  windowWidth = width;
  windowHeight = height;
}
#endif

bool BaseApplication::initializeWindow(int windowWidth, int windowHeight, const char* windowTitle){
  this->windowWidth  = windowWidth;
  this->windowHeight = windowHeight;
#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
  window = SimpleWindow::createWindow(windowWidth, windowHeight, windowTitle);
  if(!window) return false;
#endif
  return true;
}

bool BaseApplication::isRunning(){
#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
  return !glfwWindowShouldClose(window);
#else
  return true;
#endif
}

void BaseApplication::update(){

}

void BaseApplication::finalize(){
  // プログラムオブジェクトの後処理
  glDeleteProgram(program);
#if _WIN32 || (TARGET_OS_MAC && !TARGET_OS_IPHONE)
  // GLFWの終了
  glfwTerminate();
#endif
}
#ifndef _SimpleProgramObject_Defined
#define _SimpleProgramObject_Defined

#if defined __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#include <OpenGLES/ES2/gl.h>
#endif
#endif

class SimpleProgramObject{
private:
  GLuint program;
  GLuint vertexShader;
  GLuint fragmentShader;
public:
  GLuint createProgram(const GLchar* vertexShaderFileName, const GLchar* fragmentShaderFileName, const GLchar* resourcePath=nullptr);
  void linkProgram();
private:
  static bool checkProgramLinkStatus(GLuint program);
  static GLchar* readShaderFile(const char* shaderFilePath);
  static bool checkShaderCompileStatus(GLuint shader);
  static GLint createAndCompileShader(GLenum shaderType, const char* shaderFilePath);

};

#endif
#include <iostream>
#include <fstream>

#if defined _WIN32
#include <GL/glew.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glfw3dll.lib")
#pragma comment(lib, "opengl32.lib")
#endif

#if defined __APPLE__
#include <TargetConditionals.h>
#endif

#if defined TARGET_OS_MAC && !TARGET_OS_IPHONE
#define GLFW_INCLUDE_GLCOREARB
#define GL3_PROTOTYPES
#include <GLFW/glfw3.h>
#endif
#include "SimpleProgramObject.h"

//
// 与えられた引数のファイル名にシェーダコードが格納されているものとして、シェーダコードをコンパイルし、
// プログラムオブジェクトを作成する
// resourcePathがnullでない場合、resourcePathと連結してから読み込む
//
GLuint SimpleProgramObject::createProgram(const GLchar* vertexShaderFileName, const GLchar* fragmentShaderFileName, const GLchar* resourcePath){
  if(resourcePath == nullptr){
    vertexShader   = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderFileName);
    fragmentShader = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderFileName);
  }
  else{
    char* vertexShaderFilePath   = new char[strlen(resourcePath)+strlen(vertexShaderFileName)+2];
    strcpy(vertexShaderFilePath, resourcePath);
    strcat(vertexShaderFilePath, vertexShaderFileName);
    char* fragmentShaderFilePath = new char[strlen(resourcePath)+strlen(fragmentShaderFileName)+2];
    strcpy(fragmentShaderFilePath, resourcePath);
    strcat(fragmentShaderFilePath, fragmentShaderFileName);
    vertexShader   = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderFilePath);
    fragmentShader = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderFilePath);
    delete (vertexShaderFilePath);
    delete (fragmentShaderFilePath);
  }
  program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  return program;
}

//
// プログラムオブジェクトのリンク
//
void SimpleProgramObject::linkProgram(){
  // プログラムオブジェクトのリンク
  glLinkProgram(program);
  
  // リンク結果のチェック
  checkProgramLinkStatus(program);
  
  // 頂点シェーダをリリースする
  if(vertexShader){
    glDetachShader(program, vertexShader);
    glDeleteShader(vertexShader);
  }
  
  // フラグメントシェーダをリリースする
  if(fragmentShader){
    glDetachShader(program, fragmentShader);
    glDeleteShader(fragmentShader);
  }
}

//
// シェーダの作成とシェーダコードのコンパイル
//
GLint SimpleProgramObject::createAndCompileShader(GLenum shaderType, const char* shaderFilePath){
  GLchar* shaderCode = readShaderFile(shaderFilePath);
  GLuint shader = glCreateShader(shaderType);
  glShaderSource(shader, 1, (const GLchar**)&shaderCode, NULL);
  glCompileShader(shader);
  checkShaderCompileStatus(shader);
  delete(shaderCode);
  return shader;
}

//
// 頂点シェーダコードの読み込み
//
GLchar* SimpleProgramObject::readShaderFile(const char* shaderFilePath){
//  std::cout << "shaderFilePath=" << shaderFilePath << std::endl;
  std::ifstream ifs;
  ifs.open(shaderFilePath, std::ifstream::binary);
  if(ifs.fail()){
    std::cerr << "File '" << shaderFilePath << "' does not exist." << std::endl;
	  return NULL;
  }
  ifs.seekg(0, std::ios::end);
  long long length = ifs.tellg();
  GLchar* vertexShaderCode = new GLchar[length+1];
  ifs.seekg(0, std::ios::beg);
  ifs.read(vertexShaderCode, length);
  vertexShaderCode[length] = NULL;
  ifs.close();
  return vertexShaderCode;
}

//
// シェーダのコンパイルが正常に行えたかどうかのチェック
//
bool SimpleProgramObject::checkShaderCompileStatus(GLuint shader){
  GLint shaderCompileStatus;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &shaderCompileStatus);
  if(shaderCompileStatus == GL_FALSE){
    std::cerr << "Shader Compile Error: ";
    GLsizei logLength, logLengthWritten;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH , &logLength);
    GLchar *logString = new GLchar[logLength];
    glGetShaderInfoLog(shader, logLength, &logLengthWritten, logString);
    std::cerr << logString << std::endl;
    delete[] logString;
    return false;
  }
  return true;
}

/*
 * プログラムのリンクが正常に行えたかどうかのチェック
 */
bool SimpleProgramObject::checkProgramLinkStatus(GLuint program){
  GLint linkStatus;
  glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
  if(linkStatus == GL_FALSE){
    std::cerr << "Program Link Error." << std::endl;
    GLsizei logLength, logLengthWritten;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
    GLchar *logString = new GLchar[logLength];
    glGetProgramInfoLog(program, logLength, &logLengthWritten, logString);
    std::cerr << logString << std::endl;
    delete[] logString;
    return false;
  }
  return true;
}

#ifndef _SimpleWindow_Defined
#define _SimpleWindow_Defined

class SimpleWindow{
public:
  static GLFWwindow* createWindow(int width, int height, const char* title);
  static void checkOpenGLCapabilities();
};

#endif
#include <iostream>
#include <fstream>

#if defined _WIN32
#include <GL/glew.h>
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glfw3dll.lib")
#pragma comment(lib, "opengl32.lib")
#endif

#if defined __APPLE__
#define GLFW_INCLUDE_GLCOREARB
#define GL3_PROTOTYPES
#endif
#include <GLFW/glfw3.h>

#include "SimpleWindow.h"

//
// GLFWの初期化
//
GLFWwindow* SimpleWindow::createWindow(int width, int height, const char* title){
  // GLFWの初期化
  if(!glfwInit()){
    std::cerr << "glfwInit failed." << std::endl;
    return NULL;
  }
  
  // 使用するOpenGLのバージョン(4.1 Core Profile)の指定
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  
  // ウィンドウとOpenGLコンテキストの作成
  GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL);
  if(!window){
    std::cerr << "glfwCreateWindow failed." << std::endl;
    glfwTerminate();
    return NULL;
  }
  
  // 現在のウィンドウに描くようにカレントコンテキストを設定
  glfwMakeContextCurrent(window);

  // GLEWの初期化
#if defined _WIN32
  glewExperimental=GL_TRUE;
  if(glewInit()!=GLEW_OK){
    std::cerr << "glewInit failed." << std::endl;
    return NULL;
  }
#endif
  
  // OpenGLのバージョンチェック
  checkOpenGLCapabilities();
  
  return window;
}

//
// OpenGLのバージョンチェック
//
void SimpleWindow::checkOpenGLCapabilities(){
  std::cerr << "GL_VERSION: "  <<  glGetString(GL_VERSION) << std::endl;
  std::cerr << "GL_SHADING_LANGUAGE_VERSION: " <<  glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
  std::cerr << "GL_VENDOR: "   <<  glGetString(GL_VENDOR) << std::endl;
  std::cerr << "GL_RENDERER: " <<  glGetString(GL_RENDERER) << std::endl;
}