LoginSignup
44

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-05-31

はじめに

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

コンセプト

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

GitHubリポジトリ

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

Visual Studioプロジェクトの作成

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

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

miku01.png

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

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

miku02.png

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

miku03.png

プロパティシートの作成

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

miku04.png

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

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

$(SolutionDir)include

miku05.png

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

$(SolutionDir)lib

miku06.png

GLEWとGLFWのダウンロードと配置

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

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

GLFWのダウンロードページを開き「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

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

opengl32.lib
glew32s.lib
glfw3.lib

miku08.png

ソースコードの追加

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

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

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

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

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

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

miku09.png

GLFWの初期化と終了

GLFWを初期化するにはglfwInit()関数を呼び出します。また、glfwTerminate関数を呼び出すとGLFWの終了処理が実行されます。以下はGLFWを初期化して終了するプログラムです。Main.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の最初に以下の変数宣言を追加して、

    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の呼び出しの前に以下のコードを追加します。

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

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

miku10.png

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

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>の上に追加します。

#include <GL/glew.h>

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

    GLenum ret_code;

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

    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に以下の関数を追加します。

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に以下の関数を追加します。

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_stringdebug_type_to_stringdebug_severity_to_stringはそれぞれ以下のように定義します。

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";
    }
}
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";
    }
}
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";
    }
}

ウインドウ背景色の指定

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

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

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

miku11.png

リファクタリング

Init.cというソースコードを作成して、初期化関連のコードはそちらに移動します。同時にInit.hというヘッダーファイルも作成します。最終的にMain.cInit.hInit.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;
}
Init.h
#ifndef INIT_H_INCLUDE
#define INIT_H_INCLUDE

#include <GLFW/glfw3.h>

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

#endif
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";
    }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44