DirectXを使おう!まずは準備
今回は実際にDirectXを使用して、画面に色を出していきたいと思います。
それをするためにソースファイルの作成などの準備をしましょう。
前回作成したものは「GameProgramming」フォルダに入っています。その中の「Application」フォルダにプロジェクトが入っているので、ここの中にソースファイルを作成しましょう。
しかし、今から作成するソースファイル全てをこのフォルダに入れてしまうとファイルが増えすぎて大変になってくるので、上図のように「Source」フォルダを作り「DirectXフォルダ」を作成して、その中にソースファイルを作成しましょう。
今後ソースファイルを追加する時は、全てこの「Source」フォルダ以下に作成していきます。
新規テキストファイルでソースファイルを作成できたら、それをVisual Studioのプロジェクトに追加しよう。
追加の方法が分からない人は、以下のページを見てみてください。
下の図のようにできていればよいです!
「Source」や「DirectX」のようなフィルタを作成する方法は、「Application」を右クリックし「追加(D)」->「新しいフィルター(F)」
C++17を使用できるようにする
C++17の機能を使用するので、C++17を使えるように設定しておきましょう。
C++も年々バージョンが上がっていて、便利な機能がふえているのです。
Direct3Dの初期化
ではいよいよDirect3Dの初期化をしましょう。
Direct3Dとは様々な機能があるDirectXの中の、3Dグラフィックス機能のことです。
最近はDirect3Dしか更新されてないので、実質DirectX = Direct3Dのことです。
Direct3D.hに書くもの。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#pragma once // Direct3Dのライブラリを使用できるようにする #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "dxgi.lib") // Direct3Dの型・クラス・関数などを呼べるようにする #include <d3d11.h> // DirectXMath(数学ライブラリ)を使用できるようにする #include <DirectXMath.h> // ComPtrを使用できるようにする #include <wrl/client.h> using Microsoft::WRL::ComPtr; //========================================= // Direct3Dクラス //========================================= class Direct3D { public: // ※変数は今回は全てpublicにしますが、本来はprivateで隠すべき※ // Direct3Dデバイス ComPtr<ID3D11Device> m_device; // Direct3Dデバイスコンテキスト ComPtr<ID3D11DeviceContext> m_deviceContext; // スワップチェイン ComPtr<IDXGISwapChain> m_swapChain; // バックバッファーのRTビュー ComPtr<ID3D11RenderTargetView> m_backBufferView; //-------------------------------------------- // Direct3Dを初期化し、使用できるようにする関数 // hWnd : ウィンドウハンドル // width : 画面の幅 // height : 画面の高さ //-------------------------------------------- bool Initialize(HWND hWnd, int width, int height); //========================================= // 今回このクラスは、どこからでもアクセスできるように // シングルトンパターンにします // ↓↓↓以下、シングルトンパターンのコード //========================================= private: // 唯一のインスタンス用のポインタ static inline Direct3D* s_instance; // コンストラクタはprivateにする Direct3D() {} public: // インスタンス作成 static void CreateInstance() { DeleteInstance(); s_instance = new Direct3D(); } // インスタンス削除 static void DeleteInstance() { if (s_instance != nullptr) { delete s_instance; s_instance = nullptr; } } // 唯一のインスタンスを取得 static Direct3D& GetInstance() { return *s_instance; } }; // Direct3Dの唯一のインスタンスを簡単に取得するためのマクロ #define D3D Direct3D::GetInstance() |
Direct3D.cppに書くもの。量が多い・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include "Direct3D.h" #include <windows.h> bool Direct3D::Initialize(HWND hWnd, int width, int height) { //===================================================== // ファクトリー作成(ビデオ グラフィックの設定の列挙や指定に使用されるオブジェクト) //===================================================== ComPtr<IDXGIFactory> factory; if(FAILED( CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) { return false; } //===================================================== //デバイス生成(主にリソース作成時に使用するオブジェクト) //===================================================== UINT creationFlags = 0; #ifdef _DEBUG // DEBUGビルド時はDirect3Dのデバッグを有効にする // (すごく重いが細かいエラーがわかる) creationFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, // Direct3D 11.1 ShaderModel 5 D3D_FEATURE_LEVEL_11_0, // Direct3D 11 ShaderModel 5 D3D_FEATURE_LEVEL_10_1, // Direct3D 10.1 ShaderModel 4 D3D_FEATURE_LEVEL_10_0, // Direct3D 10.0 ShaderModel 4 D3D_FEATURE_LEVEL_9_3, // Direct3D 9.3 ShaderModel 3 D3D_FEATURE_LEVEL_9_2, // Direct3D 9.2 ShaderModel 3 D3D_FEATURE_LEVEL_9_1, // Direct3D 9.1 ShaderModel 3 }; // デバイスとでデバイスコンテキストを作成 D3D_FEATURE_LEVEL futureLevel; if(FAILED( D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creationFlags, featureLevels, _countof(featureLevels), D3D11_SDK_VERSION, &m_device, &futureLevel, &m_deviceContext))) { return false; } //===================================================== // スワップチェイン作成(フロントバッファに表示可能なバックバッファを持つもの) //===================================================== DXGI_SWAP_CHAIN_DESC scDesc = {}; // スワップチェーンの設定データ scDesc.BufferDesc.Width = width; // 画面の幅 scDesc.BufferDesc.Height = height; // 画面の高さ scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // バッファの形式 scDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; scDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; scDesc.BufferDesc.RefreshRate.Numerator = 0; scDesc.BufferDesc.RefreshRate.Denominator = 1; scDesc.SampleDesc.Count = 1; // MSAAは使用しない scDesc.SampleDesc.Quality = 0; // MSAAは使用しない scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // バッファの使用方法 scDesc.BufferCount = 2; // バッファの数 scDesc.OutputWindow = hWnd; scDesc.Windowed = TRUE; // ウィンドウモード scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; scDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // スワップチェインの作成 if (FAILED(factory->CreateSwapChain(m_device.Get(), &scDesc, &m_swapChain))) { return false; } // スワップチェインからバックバッファリソース取得 ComPtr<ID3D11Texture2D> pBackBuffer; if (FAILED(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)))) { return false; } // バックバッファリソース用のRTVを作成 D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {}; rtvDesc.Format = scDesc.BufferDesc.Format; rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; if(FAILED(m_device->CreateRenderTargetView(pBackBuffer.Get(), &rtvDesc, &m_backBufferView))) { return false; } //===================================================== // デバイスコンテキストに描画に関する設定を行っておく //===================================================== // バックバッファをRTとしてセット m_deviceContext->OMSetRenderTargets(1, m_backBufferView.GetAddressOf(), nullptr); // ビューポートの設定 D3D11_VIEWPORT vp = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f }; m_deviceContext->RSSetViewports(1, &vp); return true; } |
Direct3Dについて
突然すごく長いプログラムが出てきて戸惑うかと思います。Direct3Dを使用する準備は最低でもこのくらい記述が必要なのです・・・。
謎のプログラムを書き写しているだけでは力にならないので、ここでDirect3Dについて簡単にですが解説をしたいと思います。
まずDirect3D.hのクラスに用意した4つの変数について説明します。
- Direct3Dデバイス
・・・グラフィックス関係の素材を創造できるのはこいつの役割。画像とかポリゴンとか、その他いろいろ作れるのはこいつ。 - Direct3Dデバイスコンテキスト
・・・画像とかポリゴンとかの素材を使って、実際にBackBufferなどに絵を描くやつ。描画関係はこいつが主役。 - SwapChain
・・・絵を描く時はいきなりウィンドウに描かない。一度裏画面であるBackBufferに描く(チラつきをなくすため)。その裏画面であるBackBufferとウィンドウを切り替え(表示す)る役割がこいつ。こいつがいないと画面に何も表示されない。 - BackBufferView
・・・SwapChainが持っているBackBuffer(裏画面)にアクセスするためのハンドル。これがないと、BackBuffer(裏画面)を操作することができない。車で例えるなら、BackBufferが車本体で、BackBufferViewがその車を操作するためのハンドルみたいな感じ。ハンドルがないと車運転できないよね。
特によく使うのはデバイスコンテキストですね。描画に関することはこいつを使うことになります。
画像を作ったり、ポリゴン(頂点データ)を作ったりするときはデバイスの出番です。
シングルトンパターンについて
上記で作成したDirect3Dクラスは「シングルトンパターン」というプログラムの組み方をしています。
この組み方をすると、クラスの実体を2つ以上生成できないように制限し、さらにどこからでも使用できる便利なクラスになります。
ただしグローバル変数のように扱えてしまうので、多様は禁物です。
マネージャのように、1つだけ必要でどこからでも参照したいようなクラスに限り使用していきます。
ゲームループの作成
ゲームの世界は1秒間に数10~数100回の計算が高速で実行されています。常に計算や描画を実行し続けることによって、キャラクター達が動いたりして見えるわけなんです。ゲームループとは、その何度も何度も実行する処理のことです。
しかし、新規作成したこのプロジェクトでは、ゲーム用には作られておらずゲームループがありません。Application.cppの一部を書き換えて、ゲームループを作成しましょう。
下のプログラムのように、「メイン メッセージ ループ」の部分を全て消し、「ゲームループ」の部分を書き足してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
/* ※この部分を全て消す // メイン メッセージ ループ: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } ※代わりに↓↓↓のプログラムを書き足す */ // ゲームループ while (1) { if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { //============================================ // ウィンドウメッセージ処理 //============================================ // 終了メッセージがきた if (msg.message == WM_QUIT) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } //============================================ // ゲームの処理を書く //============================================ } |
「ゲームの処理を書く」と書いている場所に、毎回行いたい計算や描画を書いていきます。
Direct3Dクラスを動作させてみよう!
ようやくDirect3Dクラスを生成し動作させてみましょう。画面全体を青色で塗りつぶしてみて、きちんと動作するかテストです。
Application.cppのソースを全て記載しますが、文字の背景色が付いている部分が追加した部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
// Application.cpp : アプリケーションのエントリ ポイントを定義します。 // #include "framework.h" #include "Application.h" #include "Source/DirectX/Direct3D.h" #define MAX_LOADSTRING 100 // グローバル変数: HINSTANCE hInst; // 現在のインターフェイス WCHAR szTitle[MAX_LOADSTRING]; // タイトル バーのテキスト WCHAR szWindowClass[MAX_LOADSTRING]; // メイン ウィンドウ クラス名 // このコード モジュールに含まれる関数の宣言を転送します: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: ここにコードを挿入してください。 // グローバル文字列を初期化する LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_APPLICATION, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // アプリケーション初期化の実行: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_APPLICATION)); MSG msg; /* // メイン メッセージ ループ: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } */ // ゲームループ while (1) { if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { //============================================ // ウィンドウメッセージ処理 //============================================ // 終了メッセージがきた if (msg.message == WM_QUIT) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } //============================================ // ゲームの処理を書く //============================================ // 画面を青色で塗りつぶす float color[4] = { 0.2f, 0.2f, 1.0f, 1.0f }; D3D.m_deviceContext->ClearRenderTargetView(D3D.m_backBufferView.Get(), color); // バックバッファの内容を画面に表示 D3D.m_swapChain->Present(1, 0); } // Direct3Dインスタンス削除 Direct3D::DeleteInstance(); return (int) msg.wParam; } // // 関数: MyRegisterClass() // // 目的: ウィンドウ クラスを登録します。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_APPLICATION); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex); } // // 関数: InitInstance(HINSTANCE, int) // // 目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します // // コメント: // // この関数で、グローバル変数でインスタンス ハンドルを保存し、 // メイン プログラム ウィンドウを作成および表示します。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // グローバル変数にインスタンス ハンドルを格納する HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } // Direct3Dインスタンス作成 Direct3D::CreateInstance(); // Direct3D初期化 D3D.Initialize(hWnd, 1280, 720); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // // 関数: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: メイン ウィンドウのメッセージを処理します。 // // WM_COMMAND - アプリケーション メニューの処理 // WM_PAINT - メイン ウィンドウを描画する // WM_DESTROY - 中止メッセージを表示して戻る // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); // 選択されたメニューの解析: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: HDC を使用する描画コードをここに追加してください... EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // バージョン情報ボックスのメッセージ ハンドラーです。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } |
解説
147 148 149 150 |
// Direct3Dインスタンス作成 Direct3D::CreateInstance(); // Direct3D初期化 D3D.Initialize(hWnd, 1280, 720); |
ここでDirect3Dクラスの実体を生成し、初期設定(Initialize)を実行することで使用可能な状態にしています。
83 84 85 86 87 88 |
// 画面を青色で塗りつぶす float color[4] = { 0.2f, 0.2f, 1.0f, 1.0f }; D3D.m_deviceContext->ClearRenderTargetView(D3D.m_backBufferView.Get(), color); // バックバッファの内容を画面に表示 D3D.m_swapChain->Present(1, 0); |
ここはゲームループの中ですね。つまり何度も何度も実行される場所です。
まずcolorに青色のデータを作っています(光の3原色でRGBAの順番 全て1だと真っ白になる)。
次の行でデバイスコンテキストさんを使用してますね。描画の職人です。
こいつに「バックバッファをこの色で塗りつぶして!」と命令しています。
最後のPresentは、バックバッファに描かれた内容をウィンドウに表示する命令です。
実行結果
長い道のりでしたが、ようやく色が出ましたね!
まとめ
- Direct3D.cppとDirect3D.hに、Direct3Dを動作させるためのプログラム(Direct3Dクラス)を書いた。
- Application.cppでDirect3Dクラスの実体を生成し、実際に動作させる。
- 画面の塗りつぶしは、デバイスコンテキスト(描画の職人)にお願いする。
- ゲーム終了時には、Direct3Dクラスの実体を削除している。