今どきのOpenGLとC言語で初音ミクを表示する(その1): ウインドウの表示

# はじめに
これから複数回に分けてOpenGLとC言語を使って初音ミクの3Dモデルを表示するプログラムを作成する手順を解説します。初回である今回は、初期化処理、デバッグ処理、ウインドウの表示をするところまでを目標に進めていきます。

# コンセプト
- Windows 8.1 64bit 環境で実装。
- 開発環境にはVisual Studio Community 2013を利用。
- 今どきの OpenGL (バージョン4.5) を利用。
- C言語で実装 (C++言語ではない)。
- OpenGL APIへのアクセスには [GLEW](http://glew.sourceforge.net/) を利用。
- ウインドウの表示には [GLFW](http://www.glfw.org/) を利用。
- 簡単に自作できるものは基本的に自作する (画像やモデルの読み込み、行列計算など)。
- コードの解説よりも手順を重視 (詳細はググって調べてください)。

# GitHubリポジトリ
この記事で紹介するコードは以下のリポジトリにおいてあります。

- [OpenGL-Miku](https://github.com/lambdataro/OpenGL-Miku)

# Visual Studioプロジェクトの作成
Visual Studioを起動して、「ファイル」→「新規作成」→「プロジェクト」をクリックします。

テンプレートから「その他のプロジェクトの種類」→「Visual Studio ソリューション」を選んで「空のソリューション」を選択します。ソリューションの名前は「OpenGL-Miku」とします。

![miku01.png](https://qiita-image-store.s3.amazonaws.com/0/70306/a041ad35-9b41-445e-868f-94c3cea7c2d8.png)

ソリューションエクスプローラからソリューション「OpenGL-Miku」を右クリックして、「追加」→「新しいプロジェクト」をクリックします。

「VIsual C++」→「Win32」の「Win32 コンソール アプリケーション」を選択します。プロジェクトの名前は「Step01」とします。

![miku02.png](https://qiita-image-store.s3.amazonaws.com/0/70306/56cde593-7417-b9b1-b8a9-50b7deeaf86a.png)

Win32 アプリケーション ウィザードでアプリケーションの種類を「コンソール アプリケーション」にして、追加のオプションで「空のプロジェクト」を選択し、「Security Development Lifecycle (SDL) チェック」を選択解除します。

![miku03.png](https://qiita-image-store.s3.amazonaws.com/0/70306/9b0d0f70-d8cc-c291-5f8d-56aebd09bb8b.png)

# プロパティシートの作成
「表示」→「その他のウインドウ」→「プロパティ マネージャ」をクリックしてプロパティ マネージャを開きます。「Step01」を右クリックして「新しいプロジェクト プロパティ シートの追加」をクリックします。プロパティシートの名前は「OpenGL_GLFW」とし、保存場所をOpenGL-Mikuソリューションのディレクトリに設定します。

![miku04.png](https://qiita-image-store.s3.amazonaws.com/0/70306/05873331-a5cc-2f3c-d5e7-0316c1e9e253.png)

プロパティマネージャからOpenGL_GLFWをダブルクリックしてプロパティ ページを開きます。

共通プロパティの「C/C++」→「全般」の「追加のインクルード ディレクトリ」から「編集」を選択して、追加のインクルード ディレクトリとして以下を追加します。

```
$(SolutionDir)include
```

![miku05.png](https://qiita-image-store.s3.amazonaws.com/0/70306/05a59007-0f70-44a5-60a9-2f9f37066174.png)

「リンカー」→「全般」の「追加のライブラリ ディレクトリ」から「編集」を選択して、追加のライブラリ ディレクトリとして以下を追加します。

```
$(SolutionDir)lib
```

![miku06.png](https://qiita-image-store.s3.amazonaws.com/0/70306/69386b45-838b-64a4-d22c-9f51bd4409c4.png)

# GLEWとGLFWのダウンロードと配置
ソリューションのディレクトリにincludeフォルダとlibフォルダを作成します。ここにGLEWとGLFWのヘッダーファイルとライブラリファイルを配置します。

[GLEWのサイト](http://glew.sourceforge.net/)を開き「Binaries Windows 32-bit and 64-bit」をダウンロードして解凍します。解凍したファイルのincludeフォルダの中にあるGLフォルダを、先ほど作成したincludeフォルダ内にコピーします。続けて、libフォルダ→Releaseフォルダ→Win32フォルダの中にある`glew32s.lib`を先ほど作成したlibフォルダ内にコピーします。

[GLFWのダウンロードページ](http://www.glfw.org/download.html)を開き「32-bit Windows binaries」をダウンロードして解凍します。回答したファイルのincludeフォルダの中にあるGLFWフォルダを、先ほど作成したincludeフォルダ内にコピーします。続けてlib-vc2013フォルダの中にある`glfw3.lib`を先ほど作成したlibフォルダ内にコピーします。

# プロジェクトの設定
Visual Studioに戻り、再びプロパティマネージャからOpenGL_GLFWをダブルクリックしてプロパティ ページを開きます。

「C/C++」→「全般」の警告レベルを「レベル4」に設定します。

「C/C++」→「プリプロセッサ」のプリプロセッサの定義に以下を追加します。

```
GLEW_STATIC
```

![miku07.png](https://qiita-image-store.s3.amazonaws.com/0/70306/0826ce76-af61-8f2e-0b39-6d4b7472a0d6.png)

「リンカー」→「入力」の追加の依存ファイルを編集し以下を追加します。

```
opengl32.lib
glew32s.lib
glfw3.lib
```

![miku08.png](https://qiita-image-store.s3.amazonaws.com/0/70306/1932d004-f988-b357-35ea-8e5c6906f318.png)

# ソースコードの追加
ソリューション エクスプローラを開いて、プロジェクトStep01のソース ファイルを右クリック、「追加」→「新しい項目」をクリックします。

「C++ ファイル」を選択して名前を`Main.c`とします。

`Main.c`ファイルを開いて以下のコードを入力します。

```c:Main.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("Hello, World.\n");
    return EXIT_SUCCESS;
}
```

「デバッグ」→「デバッグなしで開始」をクリックすると、コンソールウインドウが開いて以下のように表示されるはずです。

![miku09.png](https://qiita-image-store.s3.amazonaws.com/0/70306/0aa5ebcf-bf4a-c2d2-8aa5-b5533f0e049a.png)

# GLFWの初期化と終了
GLFWを初期化するには`glfwInit()`関数を呼び出します。また、`glfwTerminate`関数を呼び出すとGLFWの終了処理が実行されます。以下はGLFWを初期化して終了するプログラムです。`Main.c`を以下のように書き替えます。

```c:Main.c
#include <stdio.h>
#include <stdlib.h>
#include <GLFW/glfw3.h>

int main(void)
{
    if (glfwInit() != GL_TRUE) {
        fprintf(stderr, "Failed to initialize GLFW.\n");
        return EXIT_FAILURE;
    }

    glfwTerminate();

    return EXIT_SUCCESS;
}
```


# ウインドウの作成
ウインドウを作成するには`glfwCreateWindow`関数を呼び出します。`glfwCreateWindow`を呼び出すとOpenGLコンテキストが作成されます。`glfwCreateWindow`関数を呼び出す前に、`glfwWindowHint`関数でOpenGLコンテキストの設定を行うことができます。`Main.c`の最初に以下の変数宣言を追加して、

```c
    GLFWwindow *window;
```

`glfwTerminate`の呼び出しの前に以下のコードを追加します。

```
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    window = glfwCreateWindow(640, 480, "Miku Hatsune", NULL, NULL);

    if (!window) {
        fprintf(stderr, "Failed to create window.\n");
        glfwTerminate();
        return EXIT_FAILURE;
    }

    glfwMakeContextCurrent(window);
```

# メッセージループの作成
`glfwTerminate`の呼び出しの前に以下のコードを追加します。

```c
    while (!glfwWindowShouldClose(window)) {
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
```

この時点で実行すると、コンソールウインドウと以下のウインドウが表示されます。

![miku10.png](https://qiita-image-store.s3.amazonaws.com/0/70306/364cee43-29c4-3715-a966-2dea5bfd02a0.png)

これまで作成したプログラム全体は以下のようになります。

```c:Main.c
#include <stdio.h>
#include <stdlib.h>
#include <GLFW/glfw3.h>

int main(void)
{
    GLFWwindow *window;

    if (glfwInit() != GL_TRUE) {
        fprintf(stderr, "Failed to initialize GLFW.\n");
        return EXIT_FAILURE;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    window = glfwCreateWindow(640, 480, "Step 01", NULL, NULL);

    if (!window) {
        fprintf(stderr, "Failed to create window.\n");
        glfwTerminate();
        return EXIT_FAILURE;
    }

    glfwMakeContextCurrent(window);

    while (!glfwWindowShouldClose(window)) {
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

    return EXIT_SUCCESS;
}
```

# GLEWの初期化
以下のインクルードディレクティブを`#include <GLFW/glfw3.h>`の上に追加します。

```c
#include <GL/glew.h>
```

`main`関数の先頭に以下の変数宣言を追加します。

```c
    GLenum ret_code;
```

`glfwMakeContextCurrent`の呼び出しの後ろに以下のコードを追加して、GLEWの初期化を行います。

```c
    glewExperimental = GL_TRUE;
    ret_code = glewInit();
    if (ret_code != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW.\n");
        fprintf(stderr, "%s\n", glewGetErrorString(ret_code));
        exit(EXIT_FAILURE);
    }
```

# GLFWのエラーコールバックを登録
`Main.c`に以下の関数を追加します。

```c:Main.c
void glfw_error_callback(int error, const char *message)
{
    fprintf(stderr, "GLFW: %s\n", message);
}
```

GLEWの初期化処理の下に以下のコードを追加します。

```
    glfwSetErrorCallback(glfw_error_callback);
```

# OpenGLのエラーコールバックを登録
`Main.c`に以下の関数を追加します。

```c:Main.c
void APIENTRY opengl_debug_callback(
    GLenum source, GLenum type, GLuint id,
    GLenum severity, GLsizei length,
    const GLchar *message, const void *param)
{
    const char *source_str = debug_source_to_string(source);
    const char *type_str = debug_type_to_string(type);
    const char *severity_str = debug_severity_to_string(severity);

    fprintf(stderr, "Source: %s, Type: %s, Severity: %s, ID: %d\n",
        source_str, type_str, severity_str, id);
    fprintf(stderr, "%s\n", message);
}
```

ここで出てくる`debug_source_to_string`、`debug_type_to_string`、`debug_severity_to_string`はそれぞれ以下のように定義します。

```c:Main.c
const char *debug_source_to_string(GLenum source)
{
    switch (source) {
    case GL_DEBUG_SOURCE_API:
        return "OpenGL";
    case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
        return "Window system";
    case GL_DEBUG_SOURCE_THIRD_PARTY:
        return "Third party";
    case GL_DEBUG_SOURCE_APPLICATION:
        return "Application";
    case GL_DEBUG_SOURCE_OTHER:
        return "Other";
    default:
        return "Unknown";
    }
}
```

```c:Main.c
const char *debug_type_to_string(GLenum type)
{
    switch (type) {
    case GL_DEBUG_TYPE_ERROR:
        return "Error";
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
        return "Deprecated behavior";
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
        return "Undefined behavior";
    case GL_DEBUG_TYPE_PORTABILITY:
        return "Portability";
    case GL_DEBUG_TYPE_PERFORMANCE:
        return "Performance";
    case GL_DEBUG_TYPE_MARKER:
        return "Marker";
    case GL_DEBUG_TYPE_PUSH_GROUP:
        return "Push group";
    case GL_DEBUG_TYPE_POP_GROUP:
        return "Pop group";
    case GL_DEBUG_TYPE_OTHER:
        return "Other";
    default:
        return "Unknown";
    }
}
```

```c:Main.c
const char *debug_severity_to_string(GLenum severity)
{
    switch (severity) {
    case GL_DEBUG_SEVERITY_HIGH:
        return "High";
    case GL_DEBUG_SEVERITY_MEDIUM:
        return "Medium";
    case GL_DEBUG_SEVERITY_LOW:
        return "Low";
    case GL_DEBUG_SEVERITY_NOTIFICATION:
        return "Notification";
    default:
        return "Unknown";
    }
}
```

# ウインドウ背景色の指定
メッセージループを以下のように書き替えて、背景色をミクっぽい色に設定します。

```c
    while (!glfwWindowShouldClose(window)) {
        glClearColor(0.6, 0.8, 0.8, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
```

この時点で実行すると以下のように表示されます。

![miku11.png](https://qiita-image-store.s3.amazonaws.com/0/70306/836d361d-2018-c0cd-3074-1ba24a5a629c.png)

# リファクタリング
`Init.c`というソースコードを作成して、初期化関連のコードはそちらに移動します。同時に`Init.h`というヘッダーファイルも作成します。最終的に`Main.c`、`Init.h`、`Init.c`は以下のようになりました。

```c:Main.c
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Init.h"

int main(void)
{
    GLFWwindow *window;
    
    window = init(640, 480, "Step 01");

    while (!glfwWindowShouldClose(window)) {
        glClearColor(0.6, 0.8, 0.8, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

    return EXIT_SUCCESS;
}
```

```c:Init.h
#ifndef INIT_H_INCLUDE
#define INIT_H_INCLUDE

#include <GLFW/glfw3.h>

/* 初期化処理 */
GLFWwindow *init(int width, int height, const char *title);

#endif
```

```c:Init.c
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "init.h"

/* GLFWデバッグ用コールバック */
void glfw_error_callback(int error, const char *message);

/* OpenGLデバッグ用コールバック関数 */
void APIENTRY opengl_debug_callback(
    GLenum source, GLenum type, GLuint id,
    GLenum severity, GLsizei length,
    const GLchar *message, const void *param);

/* デバッグsourceを文字列に変換 */
const char *debug_source_to_string(GLenum source);

/* デバッグtypeを文字列に変換 */
const char *debug_type_to_string(GLenum type);

/* デバッグseverityを文字列に変換 */
const char *debug_severity_to_string(GLenum severity);

/*==============================*
** 初期化処理
**==============================*/
GLFWwindow *init(int width, int height, const char *title)
{
    GLFWwindow *window;
    GLenum ret_code;

    if (!glfwInit()) {
        fprintf(stderr, "Failed to initialize GLFW.\n");
        exit(EXIT_FAILURE);
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    window = glfwCreateWindow(width, height, title, NULL, NULL);

    if (!window) {
        fprintf(stderr, "Failed to create window.");
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    ret_code = glewInit();
    if (ret_code != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW.\n");
        fprintf(stderr, "%s\n", glewGetErrorString(ret_code));
        exit(EXIT_FAILURE);
    }

    glfwSetErrorCallback(glfw_error_callback);

    glDebugMessageCallback(opengl_debug_callback, NULL);
    glDebugMessageControl(
        GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);

    return window;
}

/*------------------------------*
** GLFWデバッグ用コールバック
**------------------------------*/
void glfw_error_callback(int error, const char *message)
{
    fprintf(stderr, "GLFW: %s\n", message);
}

/*------------------------------*
** OpenGLデバッグ用コールバック関数
**------------------------------*/
void APIENTRY opengl_debug_callback(
    GLenum source, GLenum type, GLuint id,
    GLenum severity, GLsizei length,
    const GLchar *message, const void *param)
{
    const char *source_str = debug_source_to_string(source);
    const char *type_str = debug_type_to_string(type);
    const char *severity_str = debug_severity_to_string(severity);

    fprintf(stderr, "%s:%s[%s](%d): %s\n",
        source_str, type_str, severity_str, id, message);
}

/*------------------------------*
** デバッグsourceを文字列に変換
**------------------------------*/
const char *debug_source_to_string(GLenum source)
{
    switch (source) {
    case GL_DEBUG_SOURCE_API:
        return "OpenGL";
    case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
        return "Window system";
    case GL_DEBUG_SOURCE_THIRD_PARTY:
        return "Third party";
    case GL_DEBUG_SOURCE_APPLICATION:
        return "Application";
    case GL_DEBUG_SOURCE_OTHER:
        return "Other";
    default:
        return "Unknown";
    }
}

/*------------------------------*
** デバッグtypeを文字列に変換
**------------------------------*/
const char *debug_type_to_string(GLenum type)
{
    switch (type) {
    case GL_DEBUG_TYPE_ERROR:
        return "Error";
    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
        return "Deprecated behavior";
    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
        return "Undefined behavior";
    case GL_DEBUG_TYPE_PORTABILITY:
        return "Portability";
    case GL_DEBUG_TYPE_PERFORMANCE:
        return "Performance";
    case GL_DEBUG_TYPE_MARKER:
        return "Marker";
    case GL_DEBUG_TYPE_PUSH_GROUP:
        return "Push group";
    case GL_DEBUG_TYPE_POP_GROUP:
        return "Pop group";
    case GL_DEBUG_TYPE_OTHER:
        return "Other";
    default:
        return "Unknown";
    }
}

/*------------------------------*
** デバッグseverityを文字列に変換
**------------------------------*/
const char *debug_severity_to_string(GLenum severity)
{
    switch (severity) {
    case GL_DEBUG_SEVERITY_HIGH:
        return "High";
    case GL_DEBUG_SEVERITY_MEDIUM:
        return "Medium";
    case GL_DEBUG_SEVERITY_LOW:
        return "Low";
    case GL_DEBUG_SEVERITY_NOTIFICATION:
        return "Notification";
    default:
        return "Unknown";
    }
}
```