DirextXは強力な描画APIですが、初期化や初めの描画など、始めるまでの学習コストが非常に高いです。
本記事ではDirectX11を扱う第一歩として、初期化の方法と最も簡単な描画である「画面を塗りつぶす」処理までを紹介します。
想定環境
- Windows10以降
- Visual Studio2022
- Windows 10 SDK(Visual Studio2022と同時にインストールされるので問題なし)
- DirectX11(SDKに含まれるため、追加インストールは不要)
前提知識
-
C++の基礎構文を理解している -
WinAPIでウィンドウを作成できる
上記の記事の内容が理解できたら十分です。
コード例
本記事では、WinAPIを軸にプロジェクトを始めます。
上記の記事と同じ方法でプロジェクトのセットアップを行うため、省略してコード例から始めます。
// 必要なヘッダファイル
#include <windows.h> // Win32APを使用するためのヘッダI
#include <d3d11.h> // DirectX11のメイン機能
#include <d3dcompiler.h> // シェーダーコンパイル用(今回は未使用だが必須)
#pragma comment(lib, "d3d11.lib") // DirectX11 ライブラリをリンク
#pragma comment(lib, "d3dcompiler.lib") // シェーダーコンパイラをリンク
// グローバル変数(DirectX の主要オブジェクト)
HWND hWnd = nullptr; // ウィンドウハンドル
IDXGISwapChain* swapChain = nullptr; // フレームを画面に送るためのスワップチェイン
ID3D11Device* device = nullptr; // GPU とやり取りするデバイス
ID3D11DeviceContext* context = nullptr; // 描画命令を送るためのコンテキスト
ID3D11RenderTargetView* rtv = nullptr; // 描画先(画面)のビュー
// ウィンドウプロシージャ(イベント処理)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_DESTROY: // ウィンドウが閉じられたとき
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
// DirectX11 初期化
void InitD3D(HWND hWnd) {
// スワップチェインの設定
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1; // バッファ数(ダブルバッファリング)
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 色フォーマット(RGBA 8bit)
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 描画ターゲットとして使用
scd.OutputWindow = hWnd; // 出力先ウィンドウ
scd.SampleDesc.Count = 1; // マルチサンプリングなし
scd.Windowed = TRUE; // ウィンドウモード
// デバイスとスワップチェインを作成
D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION,
&scd, &swapChain, &device, nullptr, &context
);
// バックバッファ(画面用テクスチャ)を取得
ID3D11Texture2D* backBuffer = nullptr;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
// バックバッファからレンダーターゲットビューを作成
device->CreateRenderTargetView(backBuffer, nullptr, &rtv);
backBuffer->Release();
}
// 描画処理(画面を塗りつぶす)
void RenderFrame() {
// クリアカラー(青っぽい色)
float clearColor[4] = { 0.2f, 0.4f, 0.6f, 1.0f };
// 描画ターゲットを設定
context->OMSetRenderTargets(1, &rtv, nullptr);
// 画面をクリア(塗りつぶし)
context->ClearRenderTargetView(rtv, clearColor);
// バッファを画面に表示
swapChain->Present(1, 0);
}
// メイン関数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
// ウィンドウクラスの登録
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L,
GetModuleHandle(NULL), nullptr, nullptr, nullptr, nullptr,
L"DX11WindowClass", nullptr };
RegisterClassEx(&wc);
// ウィンドウ作成
hWnd = CreateWindow(wc.lpszClassName, L"DirectX11 Tutorial",
WS_OVERLAPPEDWINDOW, 100, 100, 800, 600,
nullptr, nullptr, wc.hInstance, nullptr);
// DirectX11 初期化
InitD3D(hWnd);
// ウィンドウ表示
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// メッセージループ
MSG msg = {};
while (msg.message != WM_QUIT) {
while (PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
RenderFrame(); // 毎フレーム必ず描画
}
// 後始末
if (rtv) rtv->Release();
if (swapChain) swapChain->Release();
if (context) context->Release();
if (device) device->Release();
UnregisterClass(wc.lpszClassName, wc.hInstance);
return 0;
}
実行結果
インクルードなど
まず、WinAPIとDirectX11を使用するためのファイルを#includeします。
#pragma comment(lib,ファイル名)でプロジェクトのプロパティを触らなくても任意のlibファイルをリンクすることができます。
本記事では"d3d11.lib"と"d3dcompiler.lib"をリンクさせています。
尚この二つのファイルはWindows SDKに同梱されているものをリンクしています。
// 必要なヘッダファイル
#include <windows.h> // Win32APを使用するためのヘッダI
#include <d3d11.h> // DirectX11のメイン機能
#include <d3dcompiler.h> // シェーダーコンパイル用(今回は未使用だが必須)
#pragma comment(lib, "d3d11.lib") // DirectX11 ライブラリをリンク
#pragma comment(lib, "d3dcompiler.lib") // シェーダーコンパイラをリンク、本記事では必要ないが必須
グローバル変数の宣言
DirectXで描画を行うために必要となる変数を宣言します。
本記事はチュートリアルのためグローバルで宣言しますが、実際の開発ではクラスにまとめます。
// グローバル変数(DirectX の主要オブジェクト)
HWND hWnd = nullptr; // ウィンドウハンドル
IDXGISwapChain* swapChain = nullptr; // フレームを画面に送るためのスワップチェイン
ID3D11Device* device = nullptr; // GPU とやり取りするデバイス
ID3D11DeviceContext* context = nullptr; // 描画命令を送るためのコンテキスト
ID3D11RenderTargetView* rtv = nullptr; // 描画先(画面)のビュー
| 変数名 | 型 | 役割 |
|---|---|---|
hWnd |
HWND |
WinAPI のウィンドウハンドル。描画結果を表示する対象のウィンドウを識別する。 |
swapChain |
IDXGISwapChain* |
バックバッファとフロントバッファを管理し、描画結果を画面に送る仕組み。 |
device |
ID3D11Device* |
GPU リソース(バッファやテクスチャ)を作成する窓口。 |
context |
ID3D11DeviceContext* |
描画命令を GPU に送る窓口。Draw() や ClearRenderTargetView() を呼び出す。 |
rtv |
ID3D11RenderTargetView* |
バックバッファを「描画先」として扱うためのビュー。ここに対して描画命令を送る。 |
DirectXの初期化
本記事では関数にDirectXの初期化をまとめています。
引数にWinAPIのウィンドウハンドルを渡すため、そちらを作成してから呼び出してください。
// DirectX11 初期化
void InitD3D(HWND hWnd) {
// スワップチェインの設定
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1; // バッファ数(ダブルバッファリング)
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 色フォーマット(RGBA 8bit)
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 描画ターゲットとして使用
scd.OutputWindow = hWnd; // 出力先ウィンドウ
scd.SampleDesc.Count = 1; // マルチサンプリングなし
scd.Windowed = TRUE; // ウィンドウモード
// デバイスとスワップチェインを作成
D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION,
&scd, &swapChain, &device, nullptr, &context
);
// バックバッファ(画面用テクスチャ)を取得
ID3D11Texture2D* backBuffer = nullptr;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
// バックバッファからレンダーターゲットビューを作成
device->CreateRenderTargetView(backBuffer, nullptr, &rtv);
backBuffer->Release();
}
初めに、DXGI_SWAP_CHAIN_DESCの初期化を行います。
こちらは先ほどグローバルに作成したIDXGISwapChain*型の初期化時に参照する値を設定しておくための構造体です。
| メンバ名 | 型 | 説明 | 本記事での設定値 | 設定値の意味 |
|---|---|---|---|---|
BufferDesc |
DXGI_MODE_DESC |
バッファの表示モード(解像度、リフレッシュレート、色フォーマットなど)を指定する。 |
Format = DXGI_FORMAT_R8G8B8A8_UNORM
|
RGBA 各 8bit の標準的な色フォーマット。最も一般的で扱いやすい。 |
SampleDesc |
DXGI_SAMPLE_DESC |
マルチサンプリング(アンチエイリアス)の設定。Count=1 で無効。 |
Count = 1 |
アンチエイリアスなし。処理が軽いことがメリット。 |
BufferUsage |
UINT |
バッファの用途。通常は DXGI_USAGE_RENDER_TARGET_OUTPUT を指定して描画ターゲットにする。 |
DXGI_USAGE_RENDER_TARGET_OUTPUT |
バックバッファを描画先として使用する。 |
BufferCount |
UINT |
バッファの枚数。通常は 2(ダブルバッファリング)。 | 1 | バックバッファを1枚だけ使用。最小構成で動作確認用。 |
OutputWindow |
HWND |
描画結果を表示するウィンドウのハンドル。 | hWnd |
作成したWinAPIのウィンドウハンドル。 |
Windowed |
BOOL |
ウィンドウモード (TRUE) かフルスクリーン (FALSE) かを指定。 |
TRUE |
ウィンドウモードで動作。開発・デバッグに適している。 |
SwapEffect |
DXGI_SWAP_EFFECT |
バッファの入れ替え方法。一般的には DXGI_SWAP_EFFECT_DISCARD を使用。 |
(未指定 → 既定値) | DirectX11 の既定値である DISCARD が使われる。描画後に内容を破棄して効率的に入れ替える。 |
Flags |
UINT |
追加オプション。例: DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH でモード切替を許可。 |
(未指定 → 0) | 特に追加機能は使わず、標準動作。 |
// スワップチェインの設定
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1; // バッファ数(ダブルバッファリング)
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 色フォーマット(RGBA 8bit)
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 描画ターゲットとして使用
scd.OutputWindow = hWnd; // 出力先ウィンドウ
scd.SampleDesc.Count = 1; // マルチサンプリングなし
scd.Windowed = TRUE; // ウィンドウモード
次に、D3D11CreateDeviceAndSwapChain()を使用して、グローバルに宣言したデバイスとスワップチェインをまとめて生成します。
引数の意味は以下の通りです。
| 引数番号 | 引数名 | 型 | 本記事での値 | 意味 |
|---|---|---|---|---|
| 1 | pAdapter |
IDXGIAdapter* |
nullptr |
使用する GPU アダプタ。nullptr を指定すると既定の GPU が選ばれる。 |
| 2 | DriverType |
D3D_DRIVER_TYPE |
D3D_DRIVER_TYPE_HARDWARE |
ハードウェア GPU を使用。最も一般的で高速。 |
| 3 | Software |
HMODULE |
nullptr |
ソフトウェアレンダラーを指定する場合に使用。今回は不要。 |
| 4 | Flags |
UINT |
0 |
デバッグや特殊機能のフラグ。0 で標準動作。 |
| 5 | pFeatureLevels |
D3D_FEATURE_LEVEL* |
nullptr |
サポートする機能レベルの配列。nullptr なら自動判定。 |
| 6 | FeatureLevels |
UINT |
0 |
配列の要素数。今回は 0(自動判定)。 |
| 7 | SDKVersion |
UINT |
D3D11_SDK_VERSION |
DirectX11 SDK のバージョン。固定値。 |
| 8 | pSwapChainDesc |
DXGI_SWAP_CHAIN_DESC* |
&scd |
スワップチェインの設定構造体。先ほど初期化した値を渡す。 |
| 9 | ppSwapChain |
IDXGISwapChain** |
&swapChain |
作成されたスワップチェインが格納される。 |
| 10 | ppDevice |
ID3D11Device** |
&device |
作成されたデバイスが格納される。 |
| 11 | pFeatureLevel |
D3D_FEATURE_LEVEL* |
nullptr |
実際に選ばれた機能レベルを受け取る。今回は不要。 |
| 12 | ppImmediateContext |
ID3D11DeviceContext** |
&context |
作成されたデバイスコンテキストが格納される。描画命令を送る窓口。 |
// デバイスとスワップチェインを作成
D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION,
&scd, &swapChain, &device, nullptr, &context
);
描画するためには、当然描画先となるテクスチャが必要になります。
swapChainのGetBuffer()から、バックバッファ(画面用のテクスチャ)を取得します。
GetBuffer()の引数は以下の通りです。
| 引数番号 | 引数名 | 型 | 引数の意味 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|---|
| 1 | Buffer |
UINT |
バッファ番号。スワップチェインが持つ複数のバッファのうち、どれを取得するかを指定する。 | 0 |
最初のバッファ(バックバッファ)を取得する。 |
| 2 | riid |
REFIID |
取得するインターフェースの型を指定する。 | __uuidof(ID3D11Texture2D) |
バッファを ID3D11Texture2D として取得する。 |
| 3 | ppSurface |
void** |
取得したバッファを格納するポインタのアドレス。 | (void**)&backBuffer |
取得したバックバッファを backBuffer に格納する。 |
// バックバッファ(画面用テクスチャ)を取得
ID3D11Texture2D* backBuffer = nullptr;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
バックバッファを作成したので、それをレンダーターゲットビュー(描画先)に指定します。
指定するにはdeviceのCreateRenderTargetView()を使用します。
引数は以下の通りです。
| 引数番号 | 引数名 | 型 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|
| 1 | pResource |
ID3D11Resource* |
backBuffer |
バックバッファ(画面用テクスチャ)を指定。 |
| 2 | pDesc |
const D3D11_RENDER_TARGET_VIEW_DESC* |
nullptr |
特殊な設定なし。既定のフォーマットでビューを作成。 |
| 3 | ppRTView |
ID3D11RenderTargetView** |
&rtv |
作成されたレンダーターゲットビューを格納する。 |
// バックバッファからレンダーターゲットビューを作成
device->CreateRenderTargetView(backBuffer, nullptr, &rtv);
最後に、メモリリークしないように、バックバッファをリリースして、初期化処理を終了とします。
backBuffer->Release();
描画処理
メッセージループ内で毎フレーム描画を更新しますが、その際の処理を関数にしておきます。
// 描画処理(画面を塗りつぶす)
void RenderFrame() {
// クリアカラー(青っぽい色)
float clearColor[4] = { 0.2f, 0.4f, 0.6f, 1.0f };
// 描画ターゲットを設定
context->OMSetRenderTargets(1, &rtv, nullptr);
// 画面をクリア(塗りつぶし)
context->ClearRenderTargetView(rtv, clearColor);
// バッファを画面に表示
swapChain->Present(1, 0);
}
本記事では画面を一色で覆いつくしますが、まずはそのための色を指定します。
floatが四つの配列で、先頭から順にRGBAの順番で指定します。
// クリアカラー(青っぽい色)
float clearColor[4] = { 0.2f, 0.4f, 0.6f, 1.0f };
描画処理では、まず初めにGPUに描画先を教える必要があります。contextのOMSetRenderTargets()を使用することで行うことができます。
引数は以下の通りです。
| 引数番号 | 引数名 | 型 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|
| 1 | NumViews |
UINT |
1 |
設定するレンダーターゲットビューの数。今回は1枚。 |
| 2 | ppRenderTargetViews |
ID3D11RenderTargetView* const* |
&rtv |
描画先ビュー。バックバッファを指定。 |
| 3 | pDepthStencilView |
ID3D11DepthStencilView* |
nullptr |
深度ステンシルビューは未使用。今回は2D描画のみ。 |
// 描画ターゲットを設定
context->OMSetRenderTargets(1, &rtv, nullptr);
次に、画面のクリアを行います。
直接的に画面を青で満たしているのはここになります。
contextのClearRenderTargetView()で画面をクリアします。
引数は以下の通りです。
| 引数番号 | 引数名 | 型 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|
| 1 | pRenderTargetView |
ID3D11RenderTargetView* |
&rtv |
描画対象のビュー。バックバッファを指定。 |
| 2 | ColorRGBA |
const FLOAT[4] |
clearColor |
RGBA の配列。{0.2f, 0.4f, 0.6f, 1.0f} → 青系の色で塗りつぶす。 |
描画処理の最後で、描画した内容を実際の画面に表示させます。
swapChainのPresent()を呼ぶことで、画面に表示させることができます。
引数は以下の通りです。
| 引数番号 | 引数名 | 型 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|
| 1 | SyncInterval |
UINT |
1 |
垂直同期を待つ。ティアリング(画面のズレ)を防ぐ。 |
| 2 | Flags |
UINT |
0 |
特殊なフラグなし。標準動作。 |
通常はバッファが二枚のダブルバッファリングとして、フロントバッファとバックバッファを切り替えるのですが、本記事では青く画面をクリアするだけですので、画面用テクスチャが一枚のシングルバッファリングを採用しています。
テクスチャが一枚の場合は表示しつつ描画も行います。
その場合でも、バックバッファをフロントに更新する処理は必要となります。
メッセージループ
// メッセージループ
MSG msg = {};
while (msg.message != WM_QUIT) {
while (PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
RenderFrame(); // 毎フレーム必ず描画
}
リアルタイム描画の場合は、GetMessage()ではなく、PeekMessage()でメッセージを取得します。
GetMessage()ではメッセージが来るまで待機をしますが、この関数の場合、メッセージがなくても処理を続けることができます。
| 引数番号 | 引数名 | 型 | 本記事での値 | 本記事での値の意味 |
|---|---|---|---|---|
| 1 | lpMsg |
LPMSG |
&msg |
取得したメッセージを格納する構造体のポインタ。 |
| 2 | hWnd |
HWND |
nullptr |
特定のウィンドウを対象にする場合のハンドル。nullptr で全ウィンドウ対象。 |
| 3 | wMsgFilterMin |
UINT |
0U |
取得するメッセージの最小値。0U でフィルタなし。 |
| 4 | wMsgFilterMax |
UINT |
0U |
取得するメッセージの最大値。0U でフィルタなし。 |
| 5 | wRemoveMsg |
UINT |
PM_REMOVE |
取得したメッセージをキューから削除するかどうか。PM_REMOVE で削除。 |
終了処理
最後に、アプリケーション終了時にメモリリークしないように後始末を行います。
// 後始末
if (rtv) rtv->Release();
if (swapChain) swapChain->Release();
if (context) context->Release();
if (device) device->Release();
UnregisterClass(wc.lpszClassName, wc.hInstance);
総括
-
VisualStudioとSDKがあれば特に新しい環境構築なしでDirectX11を使用することができる。 - 初期化は少し煩雑だが、本記事の手順でレンダリングを始めることができる。
- ゲーム開発などの場合はメッセージループに
GetMessage()ではなくPeekMessage()を使用する。
