C++で作るゲームエンジン自作シリーズ
| Part1 設計編 | Part2 ウィンドウ | Part3 OpenGL | Part4 シェーダー | Part5 テクスチャ | Part6 3Dモデル | Part7 当たり判定 |
|---|---|---|---|---|---|---|
| - | 👈 Now | - | - | - | - | - |
はじめに
ゲームエンジン作りの第一歩はウィンドウを開くこと。
「え、ウィンドウ開くだけ?簡単じゃん」
...と思うでしょ?Win32 APIで書くとこうなる:
// Win32 APIでウィンドウを開く(抜粋)
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.lpszClassName = L"GameWindow";
RegisterClassEx(&wc);
HWND hwnd = CreateWindowEx(
0, L"GameWindow", L"Game",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
nullptr, nullptr, hInstance, nullptr
);
// ... まだ続く
しかもこれWindowsだけ。macOSならCocoa、LinuxならX11を書く必要がある。
GLFWを使えば一発。
GLFWとは
GLFW(Graphics Library Framework) は、ウィンドウ作成とOpenGLコンテキスト管理を抽象化するライブラリ。
できること:
- クロスプラットフォームなウィンドウ作成
- OpenGLコンテキストの初期化
- キーボード・マウス入力の取得
- ジョイスティック対応
開発環境のセットアップ
vcpkgでインストール
# vcpkgをインストール(まだの場合)
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
# GLFWをインストール
.\vcpkg install glfw3:x64-windows
GLADのダウンロード
OpenGLの関数は実行時にロードする必要がある。GLADがこれを自動化してくれる。
- https://glad.dav1d.de/ にアクセス
- 以下を選択:
- Language: C/C++
- Specification: OpenGL
- API gl: Version 3.3
- Profile: Core
- 「Generate」をクリック
- zipをダウンロードして
external/glad/に展開
プロジェクト構成
cpp-game-engine/
├── CMakeLists.txt
├── src/
│ └── main.cpp
└── external/
└── glad/
├── include/
│ ├── glad/
│ │ └── glad.h
│ └── KHR/
│ └── khrplatform.h
└── src/
└── glad.c
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(GameEngine VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# GLFWを探す
find_package(glfw3 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
# GLADをライブラリとして追加
add_library(glad STATIC external/glad/src/glad.c)
target_include_directories(glad PUBLIC external/glad/include)
# メインの実行ファイル
add_executable(GameEngine src/main.cpp)
target_link_libraries(GameEngine PRIVATE
glfw
glad
OpenGL::GL
)
target_include_directories(GameEngine PRIVATE
${CMAKE_SOURCE_DIR}/external/glad/include
)
main.cpp
いよいよコードを書く!
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ウィンドウサイズ変更時のコールバック
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// キー入力のコールバック
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
int main() {
// GLFWの初期化
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
// OpenGLバージョンを指定(3.3 Core Profile)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// ウィンドウを作成
GLFWwindow* window = glfwCreateWindow(800, 600, "Game Engine", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// OpenGLコンテキストを現在のスレッドに設定
glfwMakeContextCurrent(window);
// GLADでOpenGL関数をロード
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
return -1;
}
// ビューポートを設定
glViewport(0, 0, 800, 600);
// コールバックを登録
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetKeyCallback(window, key_callback);
// OpenGL情報を表示
std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl;
std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
std::cout << "Vendor: " << glGetString(GL_VENDOR) << std::endl;
std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl;
// メインループ
while (!glfwWindowShouldClose(window)) {
// 背景色をクリア(紺色)
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// ダブルバッファリング:描画バッファを交換
glfwSwapBuffers(window);
// イベントを処理
glfwPollEvents();
}
// 終了処理
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
コードの解説
1. GLFWの初期化
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
GLFWを使う前に必ず glfwInit() を呼ぶ。
2. OpenGLバージョンの指定
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
OpenGL 3.3 Core Profileを使う。
- Core Profile: 古い機能(固定機能パイプライン)を削除した新しいAPI
- Compatibility Profile: 古いコードとの互換性あり(非推奨)
Core Profileを使う理由:
- 明示的で理解しやすい
- パフォーマンスが良い
- シェーダー必須(=モダンな書き方を学べる)
3. ウィンドウの作成
GLFWwindow* window = glfwCreateWindow(800, 600, "Game Engine", nullptr, nullptr);
引数:
- 幅: 800
- 高さ: 600
- タイトル: "Game Engine"
- モニター: nullptr(ウィンドウモード)
- 共有コンテキスト: nullptr(なし)
4. OpenGLコンテキストの設定
glfwMakeContextCurrent(window);
OpenGLはコンテキストという概念がある。描画命令は「現在のコンテキスト」に対して実行される。
複数ウィンドウがある場合、どのウィンドウに描画するか切り替える必要がある。
5. GLADの初期化
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
return -1;
}
OpenGLの関数ポインタをロード。これをやらないと glClearColor などが使えない。
注意: glfwMakeContextCurrent の後に呼ぶこと!
6. メインループ
while (!glfwWindowShouldClose(window)) {
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
ゲームループの基本形:
-
描画:
glClearで画面をクリア -
バッファ交換:
glfwSwapBuffersでダブルバッファを交換 -
イベント処理:
glfwPollEventsでキーボード・マウスイベントを処理
ダブルバッファリングとは
画面のチラつきを防ぐ技術。
┌─────────┐ ┌─────────┐
│ Front │ │ Back │
│ Buffer │ ←→ │ Buffer │
│ (表示) │ │ (描画) │
└─────────┘ └─────────┘
- Backバッファに描画
- 描画完了したらFrontと交換
- 交換は一瞬なのでチラつかない
ビルドと実行
# ビルド
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build --config Release
# 実行
.\build\Release\GameEngine.exe
実行結果
ウィンドウが開いて、紺色の画面が表示される!
コンソールにはOpenGLのバージョン情報とかが出る。環境によって違うよ。
ESCキーで終了できる。
トラブルシューティング
GLFWが見つからない
Could not find a package configuration file provided by "glfw3"
vcpkgのツールチェーンを指定しているか確認:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
gladLoadGLLoaderが失敗
glfwMakeContextCurrent を先に呼んでいるか確認。
ウィンドウが真っ黒
glClearColor と glClear が呼ばれているか確認。
まとめ
今回やったこと:
- GLFWでウィンドウを作成
- GLADでOpenGL関数をロード
- メインループで画面をクリア
たった50行程度で、クロスプラットフォームなウィンドウが開けた!
Win32 APIだけで書いたら300行は超える。GLFWに感謝。
次回予告
Part3: OpenGLで三角形を描く
いよいよ何かを描く!三角形を画面に表示するよ。