前回はDirextXの初期化と画面のクリア方法を紹介しました。
今回はようやく、HLSLというシェーダー言語を使用して、三角形を描画してみたいと思います。
前提知識
-
C++の基礎構文を理解している -
DirectXの初期化を行える
コード例
三角形を描画する最も簡単なコード
//-----------------------------------------------
//! @file main.cpp
//! @brief D3D11で三角形を描画する最小構成サンプル
//-----------------------------------------------
#include <windows.h>
#include <d3d11.h>
#include <dxgi.h>
#include <d3dcompiler.h>
#include <cassert>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
// グローバル変数(今回は最小構成なのでここに宣言)
HWND g_hWnd = nullptr; // ウィンドウハンドル
ID3D11Device* g_device = nullptr; // D3D11デバイス
ID3D11DeviceContext* g_context = nullptr; // D3D11デバイスコンテキスト
IDXGISwapChain* g_swapChain = nullptr; // スワップチェイン
ID3D11RenderTargetView* g_rtv = nullptr; // レンダーターゲットビュー
ID3D11VertexShader* g_vs = nullptr; // 頂点シェーダー
ID3D11PixelShader* g_ps = nullptr; // ピクセルシェーダー
ID3D11InputLayout* g_inputLayout = nullptr;// 入力レイアウト
ID3D11Buffer* g_vertexBuffer = nullptr; // 頂点バッファ
// 頂点型
struct Vertex {
float x, y, z; // 位置
float r, g, b, a; // 色
};
// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
// ウィンドウ作成
HWND CreateSimpleWindow(HINSTANCE hInstance, int width, int height) {
const wchar_t* kClass = L"D3D11TriangleClass";// ウィンドウクラス名
WNDCLASS wc = {}; // ウィンドウクラス構造体
wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ
wc.hInstance = hInstance; // インスタンスハンドル
wc.lpszClassName = kClass; // ウィンドウクラス名
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // カーソル
RegisterClass(&wc); // ウィンドウクラス登録
RECT rc = { 0, 0, width, height }; // ウィンドウサイズ設定
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); // ウィンドウサイズ調整
// ウィンドウ作成
HWND hWnd = CreateWindowEx(
0, kClass, L"D3D11 Triangle",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
rc.right - rc.left, rc.bottom - rc.top,
nullptr, nullptr, hInstance, nullptr
);
// ウィンドウ表示
ShowWindow(hWnd, SW_SHOW);
return hWnd; // ウィンドウハンドルを返す
}
// D3D11初期化(デバイス、スワップチェイン、RTV)
void InitD3D11(HWND hWnd, int width, int height) {
// スワップチェイン設定(基本形)
DXGI_SWAP_CHAIN_DESC scd = {}; // スワップチェイン記述子
scd.BufferCount = 1; // バッファ数
scd.BufferDesc.Width = width; // バッファ幅
scd.BufferDesc.Height = height; // バッファ高
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 一般的なカラー形式
scd.BufferDesc.RefreshRate.Numerator = 60; // 最小構成(厳密でなくてOK)
scd.BufferDesc.RefreshRate.Denominator = 1; // 最小構成(厳密でなくてOK)
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // レンダーターゲットとして使用
scd.OutputWindow = hWnd; // 出力ウィンドウ
scd.SampleDesc.Count = 1; // マルチサンプルなし
scd.SampleDesc.Quality = 0; // 標準品質レベル
scd.Windowed = TRUE; // ウィンドウモード
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // 旧来の最小形
UINT createFlags = 0; // デバイス作成フラグ
#if defined(_DEBUG)
createFlags |= D3D11_CREATE_DEVICE_DEBUG; // デバッグレイヤー(インストール済みなら有効)
#endif
D3D_FEATURE_LEVEL featureLevel; // フィーチャーレベル格納用
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createFlags,
nullptr, 0, // 既定のフィーチャーレベル一覧
D3D11_SDK_VERSION,
&scd, &g_swapChain,
&g_device, &featureLevel,
&g_context
);
assert(SUCCEEDED(hr) && "D3D11CreateDeviceAndSwapChain failed");
// バックバッファからレンダーターゲットビュー作成
ID3D11Texture2D* backBuffer = nullptr;
hr = g_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
assert(SUCCEEDED(hr));
hr = g_device->CreateRenderTargetView(backBuffer, nullptr, &g_rtv);
backBuffer->Release();
assert(SUCCEEDED(hr));
// ビューポート設定(画面サイズに合わせる)
D3D11_VIEWPORT vp = {}; // ビューポート構造体
vp.TopLeftX = 0; // 左上X座標
vp.TopLeftY = 0; // 左上Y座標
vp.Width = static_cast<float>(width); // 幅
vp.Height = static_cast<float>(height); // 高さ
vp.MinDepth = 0.0f; // 最小深度
vp.MaxDepth = 1.0f; // 最大深度
g_context->RSSetViewports(1, &vp); // ビューポート設定
}
// 外部HLSLをコンパイルし、シェーダーと入力レイアウトを作る
void CreateShadersAndInputLayout() {
// シェーダーブロブとエラーブロブ
ID3DBlob* vsBlob = nullptr; // 頂点シェーダーブロブ
ID3DBlob* psBlob = nullptr; // ピクセルシェーダーブロブ
ID3DBlob* errorBlob = nullptr; // エラーブロブ
// 頂点シェーダーをコンパイル
HRESULT hr = D3DCompileFromFile(
L"TriangleVS.hlsl", nullptr, nullptr,
"main", "vs_5_0",
0, 0, &vsBlob, &errorBlob
);
// コンパイル失敗時
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
errorBlob->Release();
}
assert(false && "VS compile failed: check file path or syntax");
}
// 頂点シェーダー作成
hr = g_device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &g_vs);
assert(SUCCEEDED(hr));
// ピクセルシェーダー
hr = D3DCompileFromFile(
L"TrianglePS.hlsl", nullptr, nullptr,
"main", "ps_5_0",
0, 0, &psBlob, &errorBlob
);
// コンパイル失敗時
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
errorBlob->Release();
}
assert(false && "PS compile failed: check file path or syntax");
}
// ピクセルシェーダー作成
hr = g_device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &g_ps);
assert(SUCCEEDED(hr));
// 入力レイアウト(POSITION: float3, COLOR: float4)
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, UINT(3 * sizeof(float)), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
// 入力レイアウト作成
hr = g_device->CreateInputLayout(
layout, ARRAYSIZE(layout),
vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(),
&g_inputLayout
);
assert(SUCCEEDED(hr));
// Blob解放
if (vsBlob) vsBlob->Release();
if (psBlob) psBlob->Release();
if (errorBlob) errorBlob->Release();
}
// 三角形の頂点バッファを作成
void CreateTriangleVertexBuffer() {
// NDC座標で画面中央の三角形
Vertex vertices[3] = {
{ 0.0f, 0.5f, 0.0f, 1, 0, 0, 1 }, // 上:赤
{ 0.5f, -0.5f, 0.0f, 0, 1, 0, 1 }, // 右下:緑
{ -0.5f, -0.5f, 0.0f, 0, 0, 1, 1 }, // 左下:青
};
// 頂点バッファ記述子
D3D11_BUFFER_DESC bd = {}; // バッファ記述子
bd.ByteWidth = UINT(sizeof(vertices)); // バッファサイズ
bd.Usage = D3D11_USAGE_DEFAULT; // デフォルト使用
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;// 頂点バッファとして使用
// 初期データ設定
D3D11_SUBRESOURCE_DATA initData = {}; // サブリソースデータ
initData.pSysMem = vertices; // 頂点データポインタ
// 頂点バッファ作成
HRESULT hr = g_device->CreateBuffer(&bd, &initData, &g_vertexBuffer);
assert(SUCCEEDED(hr));
}
// フレーム描画:三角形を表示
void RenderFrame() {
// クリアカラー(暗めの紺)
float clearColor[4] = { 0.05f, 0.05f, 0.08f, 1.0f };
g_context->OMSetRenderTargets(1, &g_rtv, nullptr);
g_context->ClearRenderTargetView(g_rtv, clearColor);
// IA(Input Assembler)ステージにバッファ・レイアウト・トポロジを設定
g_context->IASetInputLayout(g_inputLayout);
UINT stride = sizeof(Vertex);
UINT offset = 0;
g_context->IASetVertexBuffers(0, 1, &g_vertexBuffer, &stride, &offset);
g_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// シェーダーをバインド
g_context->VSSetShader(g_vs, nullptr, 0);
g_context->PSSetShader(g_ps, nullptr, 0);
// 3頂点を描画
g_context->Draw(3, 0);
// 画面に反映
g_swapChain->Present(1, 0);
}
// 後片付け(SAFE RELEASE)
void Cleanup() {
if (g_vertexBuffer) { g_vertexBuffer->Release(); g_vertexBuffer = nullptr; }
if (g_inputLayout) { g_inputLayout->Release(); g_inputLayout = nullptr; }
if (g_vs) { g_vs->Release(); g_vs = nullptr; }
if (g_ps) { g_ps->Release(); g_ps = nullptr; }
if (g_rtv) { g_rtv->Release(); g_rtv = nullptr; }
if (g_swapChain) { g_swapChain->Release(); g_swapChain = nullptr; }
if (g_context) { g_context->Release(); g_context = nullptr; }
if (g_device) { g_device->Release(); g_device = nullptr; }
}
// エントリポイント
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int) {
// 1) ウィンドウ作成
const int kWidth = 800;
const int kHeight = 600;
g_hWnd = CreateSimpleWindow(hInstance, kWidth, kHeight);
// 2) D3D11初期化
InitD3D11(g_hWnd, kWidth, kHeight);
// 3) シェーダー&入力レイアウト
CreateShadersAndInputLayout();
// 4) 頂点バッファ
CreateTriangleVertexBuffer();
// メッセージループ&レンダリング
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
RenderFrame();
}
}
Cleanup();
return 0;
}
//--------------------------------
//! @file TriangleVS.hlsl
//! @brief 三角形描画用頂点シェーダー
//--------------------------------
//--------------------------------
// 入力構造体
//--------------------------------
struct VSInput
{
float3 position : POSITION; // 3D座標(今回はZ=0)
float4 color : COLOR; // 頂点カラー
};
//--------------------------------
// 出力構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
//--------------------------------
//! @brief 三角形描画用頂点シェーダー
//! @param IN [in] 頂点シェーダー入力
//! @return 出力構造体
//--------------------------------
VSOutput main(VSInput IN)
{
VSOutput OUT; // 出力構造体を宣言
OUT.position = float4(IN.position, 1.0); // 位置情報を設定
OUT.color = IN.color; // 頂点カラーを設定
return OUT; // 出力構造体を返す
}
//------------------------------
//! @file TrianglePS.hlsl
//! @brief 三角形描画用ピクセルシェーダー
//------------------------------
//--------------------------------
// 頂点シェーダーから受け取った情報の構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
//--------------------------------
//! @brief 三角形描画用ピクセルシェーダー
//! @param IN [in] 頂点シェーダーからの出力
//! @return ピクセルカラー
//--------------------------------
float4 main(VSOutput IN) : SV_Target
{
return IN.color; // 補間された色が入ってくる
}
ソリューションエクスプローラーのhlslファイルを右クリックして、プロパティからプロパティウィンドウを開いてください。

ウィンドウ左から構成プロパティ->全般を選択し、ウィンドウ右の項目の種類をHLSLコンパイラへ変更してください。

TriangleVS.hlslとTrianglePS.hlslに変更を施したら、コンパイルを行います。

三色の三角形が描画されたら成功です。
グローバル変数
初めに、グローバル空間に必要な変数を宣言しておきます。
大規模になるとクラスにまとめますが、本記事では小規模のためグローバルに宣言を行います。
// グローバル変数(今回は最小構成なのでここに宣言)
HWND g_hWnd = nullptr; // ウィンドウハンドル
ID3D11Device* g_device = nullptr; // D3D11デバイス
ID3D11DeviceContext* g_context = nullptr; // D3D11デバイスコンテキスト
IDXGISwapChain* g_swapChain = nullptr; // スワップチェイン
ID3D11RenderTargetView* g_rtv = nullptr; // レンダーターゲットビュー
ID3D11VertexShader* g_vs = nullptr; // 頂点シェーダー
ID3D11PixelShader* g_ps = nullptr; // ピクセルシェーダー
ID3D11InputLayout* g_inputLayout = nullptr;// 入力レイアウト
ID3D11Buffer* g_vertexBuffer = nullptr; // 頂点バッファ
本記事で初めて出てきた型は以下の通りです。
| 型名 | 役割 |
|---|---|
ID3D11VertexShader |
頂点シェーダーを表すインターフェース。頂点データを受け取り、座標変換や頂点属性処理を行う。 |
ID3D11PixelShader |
ピクセルシェーダーを表すインターフェース。ラスタライズ後のピクセルごとに色を計算・出力する。 |
ID3D11InputLayout |
頂点バッファのデータ構造をGPUに伝えるためのインターフェース。POSITIONやCOLORなどのセマンティクスを対応付ける。 |
ID3D11Buffer |
頂点バッファやインデックスバッファなど、GPUに渡すデータを保持するためのインターフェース。今回は三角形の頂点座標と色を格納。 |
頂点情報の構造体定義
次に、シェーダーで使用する頂点情報をcpp側で構造体で定義します。
構造体名は、頂点を意味するVertexとしておきます。
// 頂点型
struct Vertex {
float x, y, z; // 位置
float r, g, b, a; // 色
};
後にシェーダーに送りたい頂点情報を定義しておきます。
本記事では座標と色以外は使用しないため、このような定義となっております。
イベント処理
次にイベント処理を書きます。
特に新しく紹介することはないです。
// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
ウィンドウの作成
次に、ウィンドウ作成処理を関数にまとめておきます。
// ウィンドウ作成
HWND CreateSimpleWindow(HINSTANCE hInstance, int width, int height) {
const wchar_t* kClass = L"D3D11TriangleClass";// ウィンドウクラス名
WNDCLASS wc = {}; // ウィンドウクラス構造体
wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ
wc.hInstance = hInstance; // インスタンスハンドル
wc.lpszClassName = kClass; // ウィンドウクラス名
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // カーソル
RegisterClass(&wc); // ウィンドウクラス登録
RECT rc = { 0, 0, width, height }; // ウィンドウサイズ設定
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); // ウィンドウサイズ調整
// ウィンドウ作成
HWND hWnd = CreateWindowEx(
0, kClass, L"D3D11 Triangle",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
rc.right - rc.left, rc.bottom - rc.top,
nullptr, nullptr, hInstance, nullptr
);
// ウィンドウ表示
ShowWindow(hWnd, SW_SHOW);
return hWnd; // ウィンドウハンドルを返す
}
DirectXの初期化
初期化関数を作成します。
// D3D11初期化(デバイス、スワップチェイン、RTV)
void InitD3D11(HWND hWnd, int width, int height) {
// スワップチェイン設定(基本形)
DXGI_SWAP_CHAIN_DESC scd = {}; // スワップチェイン記述子
scd.BufferCount = 1; // バッファ数
scd.BufferDesc.Width = width; // バッファ幅
scd.BufferDesc.Height = height; // バッファ高
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 一般的なカラー形式
scd.BufferDesc.RefreshRate.Numerator = 60; // 最小構成(厳密でなくてOK)
scd.BufferDesc.RefreshRate.Denominator = 1; // 最小構成(厳密でなくてOK)
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // レンダーターゲットとして使用
scd.OutputWindow = hWnd; // 出力ウィンドウ
scd.SampleDesc.Count = 1; // マルチサンプルなし
scd.SampleDesc.Quality = 0; // 標準品質レベル
scd.Windowed = TRUE; // ウィンドウモード
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // 旧来の最小形
UINT createFlags = 0; // デバイス作成フラグ
#if defined(_DEBUG)
createFlags |= D3D11_CREATE_DEVICE_DEBUG; // デバッグレイヤー(インストール済みなら有効)
#endif
D3D_FEATURE_LEVEL featureLevel; // フィーチャーレベル格納用
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createFlags,
nullptr, 0, // 既定のフィーチャーレベル一覧
D3D11_SDK_VERSION,
&scd, &g_swapChain,
&g_device, &featureLevel,
&g_context
);
assert(SUCCEEDED(hr) && "D3D11CreateDeviceAndSwapChain failed");
// バックバッファからレンダーターゲットビュー作成
ID3D11Texture2D* backBuffer = nullptr;
hr = g_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
assert(SUCCEEDED(hr));
hr = g_device->CreateRenderTargetView(backBuffer, nullptr, &g_rtv);
backBuffer->Release();
assert(SUCCEEDED(hr));
// ビューポート設定(画面サイズに合わせる)
D3D11_VIEWPORT vp = {}; // ビューポート構造体
vp.TopLeftX = 0; // 左上X座標
vp.TopLeftY = 0; // 左上Y座標
vp.Width = static_cast<float>(width); // 幅
vp.Height = static_cast<float>(height); // 高さ
vp.MinDepth = 0.0f; // 最小深度
vp.MaxDepth = 1.0f; // 最大深度
g_context->RSSetViewports(1, &vp); // ビューポート設定
}
D3D11_VIEWPORTは、ウィンドウのどこに描画を行うかの値を指定するための構造体です。
メンバは以下の通りです。
| メンバ | 型 | 説明 |
|---|---|---|
TopLeftX |
FLOAT |
ビューポート左上の X 座標(ピクセル単位) |
TopLeftY |
FLOAT |
ビューポート左上の Y 座標(ピクセル単位) |
Width |
FLOAT |
ビューポートの幅(ピクセル単位) |
Height |
FLOAT |
ビューポートの高さ(ピクセル単位) |
MinDepth |
FLOAT |
深度の最小値(通常 0.0f) |
MaxDepth |
FLOAT |
深度の最大値(通常 1.0f) |
// ビューポート設定(画面サイズに合わせる)
D3D11_VIEWPORT vp = {}; // ビューポート構造体
vp.TopLeftX = 0; // 左上X座標
vp.TopLeftY = 0; // 左上Y座標
vp.Width = static_cast<float>(width); // 幅
vp.Height = static_cast<float>(height); // 高さ
vp.MinDepth = 0.0f; // 最小深度
vp.MaxDepth = 1.0f; // 最大深度
g_contextのメンバRSSetViewports()でビューポート(画面に描画される領域)を作成してください。
引数は以下の通りです。
| 引数名 | 型 | 説明 |
|---|---|---|
NumViewports |
UINT |
設定するビューポートの数。通常は1 |
pViewports |
const D3D11_VIEWPORT* |
ビューポート構造体へのポインタ。配列で複数指定可能 |
g_context->RSSetViewports(1, &vp); // ビューポート設定
シェーダーのコンパイル
外部ファイルに記述したシェーダーは関数を通じてコンパイルする必要があるため、その処理を記述します。
シェーダーファイルの解説は後に行います。
// 外部HLSLをコンパイルし、シェーダーと入力レイアウトを作る
void CreateShadersAndInputLayout() {
// シェーダーブロブとエラーブロブ
ID3DBlob* vsBlob = nullptr; // 頂点シェーダーブロブ
ID3DBlob* psBlob = nullptr; // ピクセルシェーダーブロブ
ID3DBlob* errorBlob = nullptr; // エラーブロブ
// 頂点シェーダーをコンパイル
HRESULT hr = D3DCompileFromFile(
L"TriangleVS.hlsl", nullptr, nullptr,
"main", "vs_5_0",
0, 0, &vsBlob, &errorBlob
);
// コンパイル失敗時
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
errorBlob->Release();
}
assert(false && "VS compile failed: check file path or syntax");
}
// 頂点シェーダー作成
hr = g_device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &g_vs);
assert(SUCCEEDED(hr));
// ピクセルシェーダー
hr = D3DCompileFromFile(
L"TrianglePS.hlsl", nullptr, nullptr,
"main", "ps_5_0",
0, 0, &psBlob, &errorBlob
);
// コンパイル失敗時
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
errorBlob->Release();
}
assert(false && "PS compile failed: check file path or syntax");
}
// ピクセルシェーダー作成
hr = g_device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &g_ps);
assert(SUCCEEDED(hr));
// 入力レイアウト(POSITION: float3, COLOR: float4)
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, UINT(3 * sizeof(float)), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
// 入力レイアウト作成
hr = g_device->CreateInputLayout(
layout, ARRAYSIZE(layout),
vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(),
&g_inputLayout
);
assert(SUCCEEDED(hr));
// Blob解放
if (vsBlob) vsBlob->Release();
if (psBlob) psBlob->Release();
if (errorBlob) errorBlob->Release();
}
シェーダーブロブとは、データの塊のことです。
コンパイルしたシェーダーの内容を一時的に保持するために宣言します。
// シェーダーブロブとエラーブロブ
ID3DBlob* vsBlob = nullptr; // 頂点シェーダーブロブ
ID3DBlob* psBlob = nullptr; // ピクセルシェーダーブロブ
ID3DBlob* errorBlob = nullptr; // エラーブロブ
D3DCompileFromFile()を使用して頂点シェーダーファイルをコンパイルします。
引数は以下の通りです。
| 引数名 | 型 | 本記事の値 | 説明 |
|---|---|---|---|
pFileName |
LPCWSTR |
L"TriangleVS.hlsl" |
コンパイル対象のHLSLファイル名(Unicode文字列)。ここでは頂点シェーダーファイル |
pDefines |
const D3D_SHADER_MACRO* |
nullptr |
マクロ定義。未使用なので nullptr
|
pInclude |
ID3DInclude* |
nullptr |
#include の管理。未使用なので nullptr
|
pEntrypoint |
LPCSTR |
"main" |
シェーダーのエントリーポイント関数名。ここでは main
|
pTarget |
LPCSTR |
"vs_5_0" |
コンパイルターゲット(シェーダーモデル)。ここでは Vertex Shader 5.0 |
Flags1 |
UINT |
0 |
コンパイルオプション。0 はデフォルト設定 |
Flags2 |
UINT |
0 |
エフェクト用フラグ。通常は 0 |
ppCode |
ID3DBlob** |
&vsBlob |
コンパイル成功時にバイナリコードが格納される Blob |
ppErrorMsgs |
ID3DBlob** |
&errorBlob |
コンパイル失敗時にエラーメッセージが格納される Blob |
// 頂点シェーダーをコンパイル
HRESULT hr = D3DCompileFromFile(
L"TriangleVS.hlsl", nullptr, nullptr,
"main", "vs_5_0",
0, 0, &vsBlob, &errorBlob
);
コンパイルしたら、g_deviceのメンバ、CreateVertexShader()を使用してGPUに処理を登録します。
引数は以下の通りです。
| 引数名 | 型 | 本記事の値 | 説明 |
|---|---|---|---|
pShaderBytecode |
const void* |
vsBlob->GetBufferPointer() |
コンパイル済みシェーダーのバイナリコードへのポインタ |
BytecodeLength |
SIZE_T |
vsBlob->GetBufferSize() |
バイナリコードのサイズ(バイト数) |
pClassLinkage |
ID3D11ClassLinkage* |
nullptr |
動的リンク用。未使用なので nullptr
|
ppVertexShader |
ID3D11VertexShader** |
&g_vs |
作成された頂点シェーダーを受け取るポインタ |
// 頂点シェーダー作成
hr = g_device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &g_vs);
ピクセルシェーダーにも同じことを行います。
関数名が異なるだけですので、説明は省略します。
// ピクセルシェーダー
hr = D3DCompileFromFile(
L"TrianglePS.hlsl", nullptr, nullptr,
"main", "ps_5_0",
0, 0, &psBlob, &errorBlob
);
// コンパイル失敗時
if (FAILED(hr)) {
if (errorBlob) {
OutputDebugStringA((char*)errorBlob->GetBufferPointer());
errorBlob->Release();
}
assert(false && "PS compile failed: check file path or syntax");
}
// ピクセルシェーダー作成
hr = g_device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &g_ps);
assert(SUCCEEDED(hr));
D3D11_INPUT_ELEMENT_DESCでGPU に伝える頂点データを宣言します。
メンバは以下の通りです。
| メンバ名 | 型 | 説明 |
|---|---|---|
SemanticName |
LPCSTR |
シェーダー側で使うセマンティクス名(例: "POSITION", "COLOR") |
SemanticIndex |
UINT |
同じセマンティクスを複数使う場合の番号(例: 行列の各列に 0,1,2,3 を付ける) |
Format |
DXGI_FORMAT |
頂点要素のデータ型(例: DXGI_FORMAT_R32G32B32_FLOAT は float3) |
InputSlot |
UINT |
頂点バッファのスロット番号。複数バッファを使う場合に指定 |
AlignedByteOffset |
UINT |
頂点構造体内でのバイトオフセット。0 や D3D11_APPEND_ALIGNED_ELEMENT を指定 |
InputSlotClass |
D3D11_INPUT_CLASSIFICATION |
頂点ごと (D3D11_INPUT_PER_VERTEX_DATA) かインスタンスごと (D3D11_INPUT_PER_INSTANCE_DATA) か |
InstanceDataStepRate |
UINT |
インスタンシング時に何インスタンスごとに進めるか。通常は 0 |
// 入力レイアウト(POSITION: float3, COLOR: float4)
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, UINT(3 * sizeof(float)), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
g_deviceのCreateInputLayout()で、GPU側に頂点データを実際に作成します。
引数は以下の通りです。
| 引数名 | 型 | 本記事の値 | 意味 |
|---|---|---|---|
pInputElementDescs |
const D3D11_INPUT_ELEMENT_DESC* |
layout |
頂点要素の配列。POSITION(float3) と COLOR(float4) を定義 |
NumElements |
UINT |
ARRAYSIZE(layout) = 2 |
頂点要素の数。今回は2つ |
pShaderBytecode |
const void* |
vsBlob->GetBufferPointer() |
コンパイル済み頂点シェーダーのバイナリコードへのポインタ |
BytecodeLength |
SIZE_T |
vsBlob->GetBufferSize() |
バイナリコードのサイズ(バイト数) |
ppInputLayout |
ID3D11InputLayout** |
&g_inputLayout |
作成された入力レイアウトを受け取るポインタ |
// 入力レイアウト作成
hr = g_device->CreateInputLayout(
layout, ARRAYSIZE(layout),
vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(),
&g_inputLayout
);
assert(SUCCEEDED(hr));
使い終わったシェーダーブロブを解放しておきます
// Blob解放
if (vsBlob) vsBlob->Release();
if (psBlob) psBlob->Release();
if (errorBlob) errorBlob->Release();
頂点の作成
次に、三角形の頂点を作成していきます。
// 三角形の頂点バッファを作成
void CreateTriangleVertexBuffer() {
// NDC座標で画面中央の三角形
Vertex vertices[3] = {
{ 0.0f, 0.5f, 0.0f, 1, 0, 0, 1 }, // 上:赤
{ 0.5f, -0.5f, 0.0f, 0, 1, 0, 1 }, // 右下:緑
{ -0.5f, -0.5f, 0.0f, 0, 0, 1, 1 }, // 左下:青
};
// 頂点バッファ記述子
D3D11_BUFFER_DESC bd = {}; // バッファ記述子
bd.ByteWidth = UINT(sizeof(vertices)); // バッファサイズ
bd.Usage = D3D11_USAGE_DEFAULT; // デフォルト使用
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;// 頂点バッファとして使用
// 初期データ設定
D3D11_SUBRESOURCE_DATA initData = {}; // サブリソースデータ
initData.pSysMem = vertices; // 頂点データポインタ
// 頂点バッファ作成
HRESULT hr = g_device->CreateBuffer(&bd, &initData, &g_vertexBuffer);
assert(SUCCEEDED(hr));
}
まずは頂点データを作成します。
型は先ほど宣言したVertexです。
// NDC座標で画面中央の三角形
Vertex vertices[3] = {
{ 0.0f, 0.5f, 0.0f, 1, 0, 0, 1 }, // 上:赤
{ 0.5f, -0.5f, 0.0f, 0, 1, 0, 1 }, // 右下:緑
{ -0.5f, -0.5f, 0.0f, 0, 0, 1, 1 }, // 左下:青
};
次に、D3D11_BUFFER_DESC を作成して、頂点バッファの設定準備を行います。
メンバは以下の通りです。
| メンバ | 本記事の値 | 意味 |
|---|---|---|
ByteWidth |
UINT(sizeof(vertices)) |
バッファ全体のサイズ(頂点配列のサイズ分)。ここでは 3頂点分の構造体サイズ |
Usage |
D3D11_USAGE_DEFAULT |
GPU が通常使用するバッファ。CPU から直接書き込まない前提で最も一般的 |
BindFlags |
D3D11_BIND_VERTEX_BUFFER |
このバッファを「頂点バッファ」として使うことを指定 |
CPUAccessFlags |
0 |
CPU からアクセスしない(Usage が DEFAULT の場合は通常 0) |
MiscFlags |
0 |
特殊用途なし |
StructureByteStride |
0 |
構造化バッファで使う場合の要素サイズ。頂点バッファでは不要なので 0 |
// 頂点バッファ記述子
D3D11_BUFFER_DESC bd = {}; // バッファ記述子
bd.ByteWidth = UINT(sizeof(vertices)); // バッファサイズ
bd.Usage = D3D11_USAGE_DEFAULT; // デフォルト使用
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;// 頂点バッファとして使用
D3D11_SUBRESOURCE_DATAは、頂点の初期データをGPUに渡すための設定です。
メンバは以下の通りです。
| メンバ名 | 型 | 説明 |
|---|---|---|
pSysMem |
const void* |
初期データのポインタ。バッファやテクスチャを作成するときに CPU 側のメモリを指定する |
SysMemPitch |
UINT |
テクスチャ用。1行ごとのバイト数(頂点バッファでは不要なので 0) |
SysMemSlicePitch |
UINT |
テクスチャ用。1スライスごとのバイト数(頂点バッファでは不要なので 0) |
// 初期データ設定
D3D11_SUBRESOURCE_DATA initData = {}; // サブリソースデータ
initData.pSysMem = vertices; // 頂点データポインタ
準備ができたので、g_vertexBufferのCreateBuffer()で頂点バッファを作成します。
引数は以下の通りです。
| 引数名 | 本記事の値 | 意味 |
|---|---|---|
pDesc |
&bd |
バッファの性質を定義する構造体。サイズや用途(頂点バッファ)を指定 |
pInitialData |
&initData |
初期データを指定する構造体。ここでは vertices 配列を GPU にコピー |
ppBuffer |
&g_vertexBuffer |
作成された頂点バッファを受け取るポインタ |
// 頂点バッファ作成
HRESULT hr = g_device->CreateBuffer(&bd, &initData, &g_vertexBuffer);
描画処理
次に、1フレーム分の描画処理を記述します。
// フレーム描画:三角形を表示
void RenderFrame() {
// クリアカラー(暗めの紺)
float clearColor[4] = { 0.05f, 0.05f, 0.08f, 1.0f }; // RGBA
g_context->OMSetRenderTargets(1, &g_rtv, nullptr); // レンダーターゲット設定
g_context->ClearRenderTargetView(g_rtv, clearColor); // 画面クリア
// IA(Input Assembler)ステージにバッファ・レイアウト・トポロジを設定
g_context->IASetInputLayout(g_inputLayout); // 入力レイアウト設定
UINT stride = sizeof(Vertex); // 頂点サイズ
UINT offset = 0; // オフセット
g_context->IASetVertexBuffers(0, 1, &g_vertexBuffer, &stride, &offset); // 頂点バッファ設定
g_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // トポロジ設定
// シェーダーをバインド
g_context->VSSetShader(g_vs, nullptr, 0); // 頂点シェーダー設定
g_context->PSSetShader(g_ps, nullptr, 0); // ピクセルシェーダー設定
// 3頂点を描画
g_context->Draw(3, 0); // 頂点数、開始頂点位置
// 画面に反映
g_swapChain->Present(1, 0); // 垂直同期ありで表示
}
g_contextのIASetInputLayout()で、先ほど作成したg_inputLayoutをGPUに認識させます。
// IA(Input Assembler)ステージにバッファ・レイアウト・トポロジを設定
g_context->IASetInputLayout(g_inputLayout); // 入力レイアウト設定
g_contextのIASetVertexBuffers()で、頂点バッファをGPUに渡します。
引数は以下の通りです。
| 引数名 | 型 | 本記事の値 | 意味 |
|---|---|---|---|
StartSlot |
UINT |
0 |
頂点バッファをバインドする開始スロット番号。通常は 0 |
NumBuffers |
UINT |
1 |
バインドする頂点バッファの数。今回は 1 つ |
ppVertexBuffers |
ID3D11Buffer* const* |
&g_vertexBuffer |
頂点バッファのポインタ配列。ここでは 1 つだけ指定 |
pStrides |
const UINT* |
&stride (= sizeof(Vertex)) |
1頂点あたりのサイズ(バイト数)。頂点構造体のサイズ |
pOffsets |
const UINT* |
&offset (= 0) |
バッファ先頭からの読み取り開始位置。通常は 0 |
g_contextのIASetPrimitiveTopology()でトポロジを設定します。
トポロジとは、頂点をどうつなげて図形にするかのことです。
今回は、3頂点ごとに1枚の三角形として解釈を行う、D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELISTを引数にしています。
g_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // トポロジ設定
次に、g_contextのVSSetShaderとPSSetShaderを使用して、作成したシェーダーをパイプライン(頂点データから画像を作るまでの流れ)にセットします。
// シェーダーをバインド
g_context->VSSetShader(g_vs, nullptr, 0); // 頂点シェーダー設定
g_context->PSSetShader(g_ps, nullptr, 0); // ピクセルシェーダー設定
g_contextのDrawで、実際に描画を行います。
引数は以下の通りです。
| 引数名 | 型 | 本記事の値 | 意味 |
|---|---|---|---|
VertexCount |
UINT |
3 |
描画する頂点数。今回は 3 頂点を使う |
StartVertexLocation |
UINT |
0 |
頂点バッファの先頭から読み始める位置 |
// 3頂点を描画
g_context->Draw(3, 0);
リソースを片付ける関数を作成しておきます。
// 後片付け(SAFE RELEASE)
void Cleanup() {
if (g_vertexBuffer) { g_vertexBuffer->Release(); g_vertexBuffer = nullptr; }
if (g_inputLayout) { g_inputLayout->Release(); g_inputLayout = nullptr; }
if (g_vs) { g_vs->Release(); g_vs = nullptr; }
if (g_ps) { g_ps->Release(); g_ps = nullptr; }
if (g_rtv) { g_rtv->Release(); g_rtv = nullptr; }
if (g_swapChain) { g_swapChain->Release(); g_swapChain = nullptr; }
if (g_context) { g_context->Release(); g_context = nullptr; }
if (g_device) { g_device->Release(); g_device = nullptr; }
}
エントリポイント
エントリポイントとなるwWinMain()を書きます。
特に目新しいこともないので、追加の解説は省略します。
// エントリポイント
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int) {
// 1) ウィンドウ作成
const int kWidth = 800;
const int kHeight = 600;
g_hWnd = CreateSimpleWindow(hInstance, kWidth, kHeight);
// 2) D3D11初期化
InitD3D11(g_hWnd, kWidth, kHeight);
// 3) シェーダー&入力レイアウト
CreateShadersAndInputLayout();
// 4) 頂点バッファ
CreateTriangleVertexBuffer();
// メッセージループ&レンダリング
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
RenderFrame();
}
}
Cleanup();
return 0;
}
頂点シェーダー/Vertex Shader
頂点シェーダーとは、GPUの描画パイプラインにおいて頂点ごとの処理を行うプログラムです。
g_contextのIASetVertexBuffers()から渡された頂点データをVSInputとして受け取り、座標変換などを行います。
//--------------------------------
//! @file TriangleVS.hlsl
//! @brief 三角形描画用頂点シェーダー
//--------------------------------
//--------------------------------
// 入力構造体
//--------------------------------
struct VSInput
{
float3 position : POSITION; // 3D座標(今回はZ=0)
float4 color : COLOR; // 頂点カラー
};
//--------------------------------
// 出力構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
//--------------------------------
//! @brief 三角形描画用頂点シェーダー
//! @param IN [in] 頂点シェーダー入力
//! @return 出力構造体
//--------------------------------
VSOutput main(VSInput IN)
{
VSOutput OUT; // 出力構造体を宣言
OUT.position = float4(IN.position, 1.0); // 位置情報を設定
OUT.color = IN.color; // 頂点カラーを設定
return OUT; // 出力構造体を返す
}
まずは、VSInputとして、受け取る構造体をシェーダー側でも定義しておきます。
VSInputはcpp側で設定したD3D11_INPUT_ELEMENT_DESC と順番が一致する必要があります。
また、:の後にあるものはセマンティクスといい、D3D11_INPUT_ELEMENT_DESCで設定した名前と一致する必要があります。
// 入力レイアウト(POSITION: float3, COLOR: float4)
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, UINT(3 * sizeof(float)), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
//--------------------------------
// 入力構造体
//--------------------------------
struct VSInput
{
float3 position : POSITION; // 3D座標(今回はZ=0)
float4 color : COLOR; // 頂点カラー
};
VSOutputとして出力するデータの構造体を定義します。
この値はラスタライザーという、三角形をピクセルに分解する場所に渡され、ラスタライザーは後のピクセルシェーダーに値を渡します。
SV_Positionというセマンティクスは特殊で、これを定義しないと、コンパイルエラーになります。
//--------------------------------
// 出力構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
main()関数内では、受け取った値を加工します。
本記事では入力情報をそのまま出力しています。
//--------------------------------
//! @brief 三角形描画用頂点シェーダー
//! @param IN [in] 頂点シェーダー入力
//! @return 出力構造体
//--------------------------------
VSOutput main(VSInput IN)
{
VSOutput OUT; // 出力構造体を宣言
OUT.position = float4(IN.position, 1.0); // 位置情報を設定
OUT.color = IN.color; // 頂点カラーを設定
return OUT; // 出力構造体を返す
}
ピクセルシェーダー/Pixel Shader
ピクセルシェーダーとは、GPUの描画パイプラインにおいて各ピクセルの最終的な色や出力値を決定するプログラムです。
//------------------------------
//! @file TrianglePS.hlsl
//! @brief 三角形描画用ピクセルシェーダー
//------------------------------
//--------------------------------
// 頂点シェーダーから受け取った情報の構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
//--------------------------------
//! @brief 三角形描画用ピクセルシェーダー
//! @param IN [in] 頂点シェーダーからの出力
//! @return ピクセルカラー
//--------------------------------
float4 main(VSOutput IN) : SV_Target
{
return IN.color; // 補間された色が入ってくる
}
頂点シェーダーから受け取る情報を構造体として定義します。
//--------------------------------
// 頂点シェーダーから受け取った情報の構造体
//--------------------------------
struct VSOutput
{
float4 position : SV_Position; // 画面に向けての最終位置
float4 color : COLOR; // ピクセルへ渡すカラー
};
main()では頂点シェーダーからの出力を加工して、バックバッファに戻り値のピクセルを書き込みます。
引数リストの後に : セマンティクスとすることで、GPUパイプラインへどのように伝えるかを指定します。
SV_Targetは、戻り値がレンダーターゲットに書き込む色であることを意味します。
//--------------------------------
//! @brief 三角形描画用ピクセルシェーダー
//! @param IN [in] 頂点シェーダーからの出力
//! @return ピクセルカラー
//--------------------------------
float4 main(VSOutput IN) : SV_Target
{
return IN.color; // 補間された色が入ってくる
}
総括
-
描画パイプラインとはGPUが頂点データから画面に表示されるピクセルへ変換する流れのこと。 -
パイプライン内の頂点シェーダーで、座標変換などを行う。 -
パイプライン内のピクセルシェーダーで、ピクセルごとの色を決める。 -
シェーダーコードはhlslという言語で記述する。 -
hlslで書いたシェーダーはc++側でコンパイルし、ID3D11DeviceContext*を通してパイプラインに登録を行う。