はじめに
これから複数回に分けて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」とします。
ソリューションエクスプローラからソリューション「OpenGL-Miku」を右クリックして、「追加」→「新しいプロジェクト」をクリックします。
「VIsual C++」→「Win32」の「Win32 コンソール アプリケーション」を選択します。プロジェクトの名前は「Step01」とします。
Win32 アプリケーション ウィザードでアプリケーションの種類を「コンソール アプリケーション」にして、追加のオプションで「空のプロジェクト」を選択し、「Security Development Lifecycle (SDL) チェック」を選択解除します。
プロパティシートの作成
「表示」→「その他のウインドウ」→「プロパティ マネージャ」をクリックしてプロパティ マネージャを開きます。「Step01」を右クリックして「新しいプロジェクト プロパティ シートの追加」をクリックします。プロパティシートの名前は「OpenGL_GLFW」とし、保存場所をOpenGL-Mikuソリューションのディレクトリに設定します。
プロパティマネージャからOpenGL_GLFWをダブルクリックしてプロパティ ページを開きます。
共通プロパティの「C/C++」→「全般」の「追加のインクルード ディレクトリ」から「編集」を選択して、追加のインクルード ディレクトリとして以下を追加します。
$(SolutionDir)include
「リンカー」→「全般」の「追加のライブラリ ディレクトリ」から「編集」を選択して、追加のライブラリ ディレクトリとして以下を追加します。
$(SolutionDir)lib
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
「リンカー」→「入力」の追加の依存ファイルを編集し以下を追加します。
opengl32.lib
glew32s.lib
glfw3.lib
ソースコードの追加
ソリューション エクスプローラを開いて、プロジェクトStep01のソース ファイルを右クリック、「追加」→「新しい項目」をクリックします。
「C++ ファイル」を選択して名前をMain.c
とします。
Main.c
ファイルを開いて以下のコードを入力します。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("Hello, World.\n");
return EXIT_SUCCESS;
}
「デバッグ」→「デバッグなしで開始」をクリックすると、コンソールウインドウが開いて以下のように表示されるはずです。
GLFWの初期化と終了
GLFWを初期化するにはglfwInit()
関数を呼び出します。また、glfwTerminate
関数を呼び出すとGLFWの終了処理が実行されます。以下はGLFWを初期化して終了するプログラムです。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();
}
この時点で実行すると、コンソールウインドウと以下のウインドウが表示されます。
これまで作成したプログラム全体は以下のようになります。
#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
に以下の関数を追加します。
void glfw_error_callback(int error, const char *message)
{
fprintf(stderr, "GLFW: %s\n", message);
}
GLEWの初期化処理の下に以下のコードを追加します。
glfwSetErrorCallback(glfw_error_callback);
OpenGLのエラーコールバックを登録
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
はそれぞれ以下のように定義します。
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";
}
}
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";
}
}
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();
}
この時点で実行すると以下のように表示されます。
リファクタリング
Init.c
というソースコードを作成して、初期化関連のコードはそちらに移動します。同時にInit.h
というヘッダーファイルも作成します。最終的にMain.c
、Init.h
、Init.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;
}
#ifndef INIT_H_INCLUDE
#define INIT_H_INCLUDE
#include <GLFW/glfw3.h>
/* 初期化処理 */
GLFWwindow *init(int width, int height, const char *title);
#endif
#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";
}
}