0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DirectX11】まずは青い画面を表示させよう!

Posted at

DirextXは強力な描画APIですが、初期化や初めの描画など、始めるまでの学習コストが非常に高いです。
本記事ではDirectX11を扱う第一歩として、初期化の方法と最も簡単な描画である「画面を塗りつぶす」処理までを紹介します。

想定環境

  • Windows10以降
  • Visual Studio2022
  • Windows 10 SDK(Visual Studio2022と同時にインストールされるので問題なし)
  • DirectX11(SDKに含まれるため、追加インストールは不要)

前提知識

  • C++の基礎構文を理解している
  • WinAPIでウィンドウを作成できる

上記の記事の内容が理解できたら十分です。

コード例

本記事では、WinAPIを軸にプロジェクトを始めます。
上記の記事と同じ方法でプロジェクトのセットアップを行うため、省略してコード例から始めます。

main.cpp
// 必要なヘッダファイル
#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;
}

実行結果

青い画面が描画されました。
スクリーンショット (75).png

インクルードなど

まず、WinAPIDirectX11を使用するためのファイルを#includeします。
#pragma comment(lib,ファイル名)でプロジェクトのプロパティを触らなくても任意のlibファイルをリンクすることができます。
本記事では"d3d11.lib"と"d3dcompiler.lib"をリンクさせています。
尚この二つのファイルはWindows SDKに同梱されているものをリンクしています。

main.cpp
// 必要なヘッダファイル
#include <windows.h>        // Win32APを使用するためのヘッダI
#include <d3d11.h>          // DirectX11のメイン機能
#include <d3dcompiler.h>    // シェーダーコンパイル用(今回は未使用だが必須)
#pragma comment(lib, "d3d11.lib")        // DirectX11 ライブラリをリンク
#pragma comment(lib, "d3dcompiler.lib")  // シェーダーコンパイラをリンク、本記事では必要ないが必須

グローバル変数の宣言

DirectXで描画を行うために必要となる変数を宣言します。
本記事はチュートリアルのためグローバルで宣言しますが、実際の開発ではクラスにまとめます。

main.cpp
// グローバル変数(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のウィンドウハンドルを渡すため、そちらを作成してから呼び出してください。

main.cpp
// 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) 特に追加機能は使わず、標準動作。
main.cpp
 // スワップチェインの設定
    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 作成されたデバイスコンテキストが格納される。描画命令を送る窓口。
main.cpp
 // デバイスとスワップチェインを作成
    D3D11CreateDeviceAndSwapChain(
        nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
        nullptr, 0, D3D11_SDK_VERSION,
        &scd, &swapChain, &device, nullptr, &context
    );

描画するためには、当然描画先となるテクスチャが必要になります。
swapChainGetBuffer()から、バックバッファ(画面用のテクスチャ)を取得します。
GetBuffer()の引数は以下の通りです。

引数番号 引数名 引数の意味 本記事での値 本記事での値の意味
1 Buffer UINT バッファ番号。スワップチェインが持つ複数のバッファのうち、どれを取得するかを指定する。 0 最初のバッファ(バックバッファ)を取得する。
2 riid REFIID 取得するインターフェースの型を指定する。 __uuidof(ID3D11Texture2D) バッファを ID3D11Texture2D として取得する。
3 ppSurface void** 取得したバッファを格納するポインタのアドレス。 (void**)&backBuffer 取得したバックバッファを backBuffer に格納する。
main.cpp
// バックバッファ(画面用テクスチャ)を取得
    ID3D11Texture2D* backBuffer = nullptr;
    swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);

バックバッファを作成したので、それをレンダーターゲットビュー(描画先)に指定します。
指定するにはdeviceCreateRenderTargetView()を使用します。
引数は以下の通りです。

引数番号 引数名 本記事での値 本記事での値の意味
1 pResource ID3D11Resource* backBuffer バックバッファ(画面用テクスチャ)を指定。
2 pDesc const D3D11_RENDER_TARGET_VIEW_DESC* nullptr 特殊な設定なし。既定のフォーマットでビューを作成。
3 ppRTView ID3D11RenderTargetView** &rtv 作成されたレンダーターゲットビューを格納する。
main.cpp
   // バックバッファからレンダーターゲットビューを作成
    device->CreateRenderTargetView(backBuffer, nullptr, &rtv);

最後に、メモリリークしないように、バックバッファをリリースして、初期化処理を終了とします。

main.cpp
    backBuffer->Release();

描画処理

メッセージループ内で毎フレーム描画を更新しますが、その際の処理を関数にしておきます。

main.cpp
// 描画処理(画面を塗りつぶす)
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の順番で指定します。

main.cpp
    // クリアカラー(青っぽい色)
    float clearColor[4] = { 0.2f, 0.4f, 0.6f, 1.0f };

描画処理では、まず初めにGPUに描画先を教える必要があります。contextOMSetRenderTargets()を使用することで行うことができます。
引数は以下の通りです。

引数番号 引数名 本記事での値 本記事での値の意味
1 NumViews UINT 1 設定するレンダーターゲットビューの数。今回は1枚。
2 ppRenderTargetViews ID3D11RenderTargetView* const* &rtv 描画先ビュー。バックバッファを指定。
3 pDepthStencilView ID3D11DepthStencilView* nullptr 深度ステンシルビューは未使用。今回は2D描画のみ。
main.cpp
    // 描画ターゲットを設定
    context->OMSetRenderTargets(1, &rtv, nullptr);

次に、画面のクリアを行います。
直接的に画面を青で満たしているのはここになります。
contextClearRenderTargetView()で画面をクリアします。
引数は以下の通りです。

引数番号 引数名 本記事での値 本記事での値の意味
1 pRenderTargetView ID3D11RenderTargetView* &rtv 描画対象のビュー。バックバッファを指定。
2 ColorRGBA const FLOAT[4] clearColor RGBA の配列。{0.2f, 0.4f, 0.6f, 1.0f} → 青系の色で塗りつぶす。

描画処理の最後で、描画した内容を実際の画面に表示させます。
swapChainPresent()を呼ぶことで、画面に表示させることができます。
引数は以下の通りです。

引数番号 引数名 本記事での値 本記事での値の意味
1 SyncInterval UINT 1 垂直同期を待つ。ティアリング(画面のズレ)を防ぐ。
2 Flags UINT 0 特殊なフラグなし。標準動作。

通常はバッファが二枚のダブルバッファリングとして、フロントバッファとバックバッファを切り替えるのですが、本記事では青く画面をクリアするだけですので、画面用テクスチャが一枚のシングルバッファリングを採用しています。
テクスチャが一枚の場合は表示しつつ描画も行います。
その場合でも、バックバッファをフロントに更新する処理は必要となります。

メッセージループ

main.cpp
    // メッセージループ
    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 で削除。

終了処理

最後に、アプリケーション終了時にメモリリークしないように後始末を行います。

main.cpp
// 後始末
    if (rtv) rtv->Release();
    if (swapChain) swapChain->Release();
    if (context) context->Release();
    if (device) device->Release();

    UnregisterClass(wc.lpszClassName, wc.hInstance);

総括

  • VisualStudioSDKがあれば特に新しい環境構築なしでDirectX11を使用することができる。
  • 初期化は少し煩雑だが、本記事の手順でレンダリングを始めることができる。
  • ゲーム開発などの場合はメッセージループにGetMessage()ではなくPeekMessage()を使用する。
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?