DirectX11 の初歩的なプログラミングを紹介します。
グラフィックス API の入門でおなじみの三角形を映すだけのプログラムを作成します。
メインウィンドウの生成処理と DirectX が行う 3D 描画の処理を明確に分けることを意識して進めます。
サンプルコードは GitHubのリポジトリ に置きました。
メインウィンドウの生成に関して
GUI アプリケーションを作成する際に重要なことの一つが使用するフレームワークの選択です。
今回は三角形を映すだけのシンプルな機能しか持たせないので、ウィンドウがメインのものしかない簡素な GUI を作成します。Win32API を使いますが一から作ることはせずに、Visual Studio のプロジェクトテンプレートから作成して手間を省きます。
プロジェクトの作成
開発環境は Visual Studio 2019 Preview を使用していますが、DirectX11 の SDK があれば特にバージョンにこだわる必要はありません。
Visual Studio を起動し新しいプロジェクトの作成を選択し、プロジェクトテンプレートを選ぶ画面で下記画像の様に「Windows デスクトップアプリケーション」を選択し[次へ]をクリックします。
今回のサンプルではプロジェクト名・ソリューション名を DrawPrimitive と付けました。
クラスの作成
DirectX による描画処理を担うクラスを作成します。クラス名は CDxGraphic とし、メンバー変数とメソッドの定義を DxGraphic.h に、メソッドの実装を DxGraphic.cpp に記述します。
ヘッダーファイルを下記に記載します。C++ ファイルに関しては GitHubのページ を参照してください。
#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のページ を参考にしてください。
メインウィンドウ側での呼び出し
エントリーポイント及びメインウィンドウの生成は自動生成された DrawPrimitive.cpp に記述されています。
このファイルに DxGraphic.h をインクルードして、処理を呼び出します。
(2020.9.26追記 GetWindowRect 関数を呼び出していた箇所を GetClientRect 関数に修正しました。)
// 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;
}
=== 以下省略 ===
プログラムの実行
参考
グラフィックパイプラインの概要(マイクロソフト公式)
DirectX11シェーダー入門 | ZeroGram
☆PROJECT ASURA☆ [Direct3D 11] 『Direct3D 11 再入門』
更新履歴
2019/09/24 : 記事投稿 & 編集
2019/12/29 : 解説記事を投稿
2020/09/26 : 微修正