DirectX11 プログラミング事始め

DirectX11 の初歩的なプログラミングを紹介します。
グラフィックス API の入門でおなじみの三角形を映すだけのプログラムを作成します。
メインウィンドウの生成処理と DirectX が行う 3D 描画の処理を明確に分けることを意識して進めます。
サンプルコードは [GitHubのリポジトリ](https://github.com/Tana0910/Samples_DirectX) に置きました。
# メインウィンドウの生成に関して
GUI アプリケーションを作成する際に重要なことの一つが使用するフレームワークの選択です。
今回は三角形を映すだけのシンプルな機能しか持たせないので、ウィンドウがメインのものしかない簡素な GUI を作成します。Win32API を使いますが一から作ることはせずに、Visual Studio のプロジェクトテンプレートから作成して手間を省きます。
# プロジェクトの作成
開発環境は Visual Studio 2019 Preview を使用していますが、DirectX11 の SDK があれば特にバージョンにこだわる必要はありません。
Visual Studio を起動し新しいプロジェクトの作成を選択し、プロジェクトテンプレートを選ぶ画面で下記画像の様に「Windows デスクトップアプリケーション」を選択し[次へ]をクリックします。
今回のサンプルではプロジェクト名・ソリューション名を DrawPrimitive と付けました。

![makeproject.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500701/76bc898e-6526-66c9-77f0-8114421ed2c5.png)
# クラスの作成
DirectX による描画処理を担うクラスを作成します。クラス名は CDxGraphic とし、メンバー変数とメソッドの定義を DxGraphic.h に、メソッドの実装を DxGraphic.cpp に記述します。
ヘッダーファイルを下記に記載します。C++ ファイルに関しては [GitHubのページ](https://github.com/Tana0910/Samples_DirectX/blob/master/DrawPrimitive/DrawPrimitive/DxGraphic.cpp) を参照してください。

```C++:DxGraphic.h
#pragma once
// M_PI
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>
#include <fstream>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")

// D3D
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "D3DCompiler.lib")

// Comptr
#include <atlcomcli.h>

class CDxGraphic
{
private:
	HWND m_WindowHandle = NULL;
	// 機能レベル, フォーマット
	D3D_FEATURE_LEVEL featurelevel = D3D_FEATURE_LEVEL_11_0;
	UINT swapchaincount = 1;
	DXGI_FORMAT swapchainformat = DXGI_FORMAT_B8G8R8A8_UNORM;
	DXGI_FORMAT depthstencilformat = DXGI_FORMAT_D24_UNORM_S8_UINT;
	DXGI_SAMPLE_DESC sampledesc = { 1, 0 };

	// コアとなる処理を行うための変数
	CComPtr<ID3D11Device> device;
	CComPtr<ID3D11DeviceContext> context;
	CComPtr<IDXGISwapChain> swapchain;
	CComPtr<ID3D11Texture2D> backbuffer;
	CComPtr<ID3D11RenderTargetView> rtv;
	CComPtr<ID3D11Texture2D> depthtex;
	CComPtr<ID3D11DepthStencilView> dsv;
	CComPtr<ID3D11RasterizerState> rs;
	CComPtr<ID3D11DepthStencilState> dss;
	CComPtr<ID3D11VertexShader> vertexshader;
	CComPtr<ID3D11GeometryShader> geometryshader;
	CComPtr<ID3D11PixelShader> pixelshader;
	CComPtr<ID3D11InputLayout> inputlayout;

	// 定数バッファ
	CComPtr<ID3D11Buffer> matrixbuffer;
	CComPtr<ID3D11Buffer> vertexbuffer;
	UINT numindices = 0;

	// DirectX算術用マトリックス
	DirectX::XMMATRIX d3dworldmatrix = DirectX::XMMatrixIdentity();
	DirectX::XMMATRIX d3dviewmatrix = DirectX::XMMatrixIdentity();
	DirectX::XMMATRIX d3dprojmatrix = DirectX::XMMatrixIdentity();

	struct Vertex
	{
		float position[3];  // (x, y, z)
		float color[4];     // (r, g, b, a)
	};

	std::vector<Vertex> InputData =
	{
		{{ 0.0f, 0.0f, 1.732051f}, {1.0f, 0.0f, 0.0f, 1.0f}},
		{{-1.0f, 0.0f,   0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}},
		{{ 1.0f, 0.0f,   0.0f}, {0.0f, 0.0f, 1.0f, 1.0f}}
	};

	struct CoordColor
	{
		DirectX::XMFLOAT3 coord;
		DirectX::XMFLOAT4 color;
	};

	struct MatrixBuffer
	{
		DirectX::XMMATRIX matproj;
		DirectX::XMMATRIX matview;
		DirectX::XMMATRIX matworld;
	};

	bool CreateDeviceAndSwapChain(int w, int h);

	bool CreateRenderTarget();

	bool CreateDefaultRasterizerState();

	bool CreateDepthStencilState();

	bool CreateStencilBuffer(int w, int h);

	bool CreateShaderFromCompiledFiles();

	bool CreateConstantBuffer();

	void ReleaseComPtr();

public:
	CDxGraphic();
	~CDxGraphic();

	void SetWindowHandle(HWND hWnd);

	bool InitD3D(int w, int h);

	void Render();

	void LoadSampleData(int w, int h);

	void UpdateMatrices(int w, int h);

	bool ResizeView(int w, int h);
};

```
InputData が今回表示させる三角形のデータです。各頂点は位置座標 (x, y, z) と色情報 (red, green, blue, alpha) を持っています。
# シェーダーファイルの作成
GPU で行う処理をシェーダーファイル(HLSL)に記述します。
[新しいファイルの追加]ウィンドウで[Visual C++]の欄にある[HLSL]をクリックし、頂点シェーダーファイルを選び VertexShader.hlsl を追加します。
ジオメトリーシェーダーファイル、ピクセルシェーダーファイルも同様の手順で追加します。お好みでフィルターとフォルダを作成してファイルを整理するのもありです。
シェーダーファイルは Shader Model 5.0 でコンパイルします。
この設定を行うにはソリューションエクスプローラーで各シェーダーファイルを右クリックしプロパティページを開き、HLSL コンパイラ欄の全般をクリックし、シェーダーモデルの項目からShader Model 5.0 (/5_0) を選択します。
各シェーダーファイルの記述は [GitHubのページ](https://github.com/Tana0910/Samples_DirectX/tree/master/DrawPrimitive/DrawPrimitive/Shader) を参考にしてください。
# メインウィンドウ側での呼び出し
エントリーポイント及びメインウィンドウの生成は自動生成された DrawPrimitive.cpp に記述されています。
このファイルに DxGraphic.h をインクルードして、処理を呼び出します。
(2020.9.26追記 GetWindowRect 関数を呼び出していた箇所を GetClientRect 関数に修正しました。)

```C++:DrawPrimitive.cpp
// DrawPrimitive.cpp : アプリケーションのエントリ ポイントを定義します。

//

#include "framework.h"
#include "DrawPrimitive.h"
#include "DxGraphic.h"
#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名
CDxGraphic g_dxgra;                             // DirectX 描画処理

// このコード モジュールに含まれる関数の宣言を転送します:
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_DRAWPRIMITIVE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーション初期化の実行:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DRAWPRIMITIVE));

    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

//
//   関数: 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;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   g_dxgra.SetWindowHandle(hWnd);
   RECT rc;
   GetClientRect(hWnd, &rc);
   int w = rc.right - rc.left;
   int h = rc.bottom - rc.top;
   if (g_dxgra.InitD3D(w, h))
   {
	   g_dxgra.LoadSampleData(w, h);
   }

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウを描画する
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	RECT rc;
	GetClientRect(hWnd, &rc);
	int w = rc.right - rc.left;
	int h = rc.bottom - rc.top;
    switch (message)
    {
	case WM_SIZE:
		if (g_dxgra.ResizeView(w, h))
		{
			g_dxgra.UpdateMatrices(w, h);
			g_dxgra.Render();
		}
		break;
    case WM_COMMAND:
=== 中略 ===
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
=== 以下省略 ===
```
# プログラムの実行
![drawprimitive.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500701/5b92be93-ba35-4539-4186-5c18292a12ff.png)

# 参考
[グラフィックパイプラインの概要(マイクロソフト公式)](https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline)
[DirectX11シェーダー入門 | ZeroGram](https://zerogram.info/?p=1933)
[☆PROJECT ASURA☆ [Direct3D 11] 『Direct3D 11 再入門』](http://www.project-asura.com/program/d3d11/d3d11_001.html)

# 更新履歴
2019/09/24 : 記事投稿 & 編集
2019/12/29 : [解説記事](https://qiita.com/Tana0910/items/bae047c6c26d7def7706)を投稿
2020/09/26 : 微修正