【OpenGL】エンジンを使わないゲーム開発 -ウィンドウの表示-【1】


緒論

前回までにGLUTを用いたOpenGLの開発を勉強していたが,前回の記事にて古いプログラムであることを教えていただいたため,色々考えて今回からGLFWを用いたOpenGL開発にシフトしていく.
このシリーズを通して目指したいのはアニメ『NEW GAME!!』の劇中にて,ねねっちの製作していたような2Dアクションゲームの製作である(以下の画像がねねっちが半年で製作したゲーム).

C++とOpenGLを使用して,プログラムの可読性を高め,かつオブジェクト指向を意識したプログラムを書けるように心がけたい.今回の目的はGLFWによるウィンドウの表示を目的とする.

参考文献
GLFW-Documentation
GLFWによるOpenGL入門
【OpenGLでゲームを作る】GLFWを使ってウインドウを表示する

理論

GLUTとの違い

描画の部分が大きく変わっている印象を受けた.
GLUTではglBegin()からglEnd()のなかで描画を行うことができ,頂点の指定や,色の指定などにおいて簡単に描画を行っていくことができた.
しかし,GLFWにおいて,描画の難易度が上がっている.

シェーダは、GLSLと呼ばれる言語でプログラムできます。GLシェーダ言語(GL Shader Language)はOpenGLの一部です。CやJavaとは異なり、GLSLは実行時にコンパイルされます。これはアプリケーションを起動するたびにシェーダは再度コンパイルされることを意味します。

とのことで,これはOpenGLチュートリアルや先に示した参考文献に詳しく書かれているため割愛.

方法

1. GLFWの初期化

    if (!glfwInit())
    {
        std::cerr << "Can't initialize GLFW\n" << std::endl;
        return 1;
    }

まずはmain文中にて,GLFWの初期化を行う.
GLFWの関数を使用する前に必要である.失敗した場合はエラー分が出るようになってる.

int glfwInit(void)

この関数は、GLFWライブラリを初期化します。ほとんどのGLFW機能を使用する前に、GLFWを初期化する必要があります。また、初期化中または初期化後に割り当てられたリソースを解放するために、GLFWを終了する必要があります。
この関数が失敗した場合、戻る前にglfwTerminateを呼び出します。成功した場合、アプリケーションを終了する前にglfwTerminateを呼び出す必要があります。

2. ウィンドウの作成

    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(windowSize.x, windowSize.y, "Hello World", NULL, NULL);
    if (!window)
    {
        std::cerr << "Can't create GLFW window." << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

ウィンドウ作成に必要な一連の流れ.
WindowHintでCreateWindowのための設定をして,ウィンドウオブジェクトに作成したオブジェクトを代入といった流れだと思う.これもウィンドウの作成に失敗した場合はエラー分が表示されるようになっている.

void glfwWindowHint(int hint, int value)

この関数は、glfwCreateWindowの次の呼び出しのヒントを設定します。ヒントは、設定されると、この関数またはglfwDefaultWindowHintsの呼び出しによって変更されるまで、またはライブラリが終了するまで、値を保持します。
この関数で設定できるのは整数値のヒントのみです。文字列値のヒントはglfwWindowHintStringで設定されます。

hintに設定したいことを入れて,valueにhintで設定する値をいれる.
設定が色々あるみたいだが,調べても少し難しかったため,参考文献中の設定をそのまま流用している.Windowhintに色々なhintとその設定値が記載されている.
やっぱり公式リファレンスが一番わかりやすいね.

GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share)

GLFWwindowのオブジェクトは、ウィンドウとコンテキストの両方をカプセル化します。これらはglfwCreateWindowで作成され、glfwDestroyWindow、または存在する場合はglfwTerminateで破棄されます。ウィンドウとコンテキストは不可分にリンクされているため、オブジェクトポインターはコンテキストとウィンドウハンドルの両方として使用されます。

widthとheightにウィンドウの大きさを入れてtitleにウィンドウのタイトルを入れる.monitorはフルスクリーンなどの設定なので今回はNULLで.

void glfwMakeContextCurrent(GLFWwindow* window)

この関数は、指定したウィンドウのOpenGLまたはOpenGL ESコンテキストを呼び出しスレッドで処理対象にします。コンテキストは、一度に1つのスレッドでのみ最新にする必要があり、各スレッドは一度に1つの現在のコンテキストのみを持つことができます。

windowに処理対象にしたいウィンドウオブジェクトを指定する.

3. ウィンドウの色付けとループ処理

    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);

    while (!glfwWindowShouldClose(window))
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwWaitEvents();
    }

while文にはウィンドウを閉じるまで(ウィンドウが開いている間)繰り返したい処理を書く.

void glfwSwapBuffers(GLFWwindow* window)

この関数は、指定されたウィンドウのフロントバッファとバックバッファを交換します。スワップ間隔がゼロより大きい場合、GPUドライバーはバッファーをスワップする前に、指定された数の画面更新を待機します。

画面の更新と考えていいのかな.毎回更新したいのでループ文の中に記述しているはず.

void glfwWaitEvents(void)

この関数は、イベントキューで少なくとも1つのイベントが使用可能になるまで、呼び出しスレッドをスリープ状態にします。1つ以上のイベントが使用可能になると、glfwPollEventsとまったく同じように動作します。つまり、キュー内のイベントが処理され、関数はすぐに戻ります。イベントを処理すると、それらのイベントに関連付けられたウィンドウおよび入力コールバックが呼び出されます。

新しい入力があるまでウィンドウの内容の更新をスリープさせるもの.glfwPollEventsにするとループのたびに更新してくので継続的にレンダリングする場合はこっちの方が良い.

結果

  • ウィンドウが表示できた!

main.cpp
main.cpp
#include <stdio.h>
#include <iostream>
#include <GL/glew.h>
#include <OpenGL/OpenGL.h>
#include <GLFW/glfw3.h>
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

#include "glm.hpp"

using namespace glm;

ivec2 windowSize = {800*16/9,800};

int main(void)
{
    // GLFW を初期化する
    if (!glfwInit())
    {
        std::cerr << "Can't initialize GLFW." << std::endl;
        return 1;
    }

    // ウィンドウの作成
    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(windowSize.x, windowSize.y, "Hello World", NULL, NULL);
    if (!window)
    {
        // ウィンドウが作成できなかった
        std::cerr << "Can't create GLFW window." << std::endl;
        glfwTerminate();
        return -1;
    }

    // 現在のウィンドウを処理対象にする
    glfwMakeContextCurrent(window);
    // 背景色を指定する
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);

    // ループ
    while (!glfwWindowShouldClose(window))
    {
        // ウィンドウの振りつぶし
        glClear(GL_COLOR_BUFFER_BIT);

        // カラーバッファの入れ替え
        glfwSwapBuffers(window);

        // イベントの発生待ち
        glfwWaitEvents();
    }

    glfwTerminate();
    return 0;
}