DirectX11によるポリゴン描画のための準備処理実装方法
概要
最終更新日:2020/03/03
DirectX11でポリゴンを描画するために必要な準備について書いた記事です。
この記事は以下の内容を知りたい方に向けて書いています。
- DirectX11でポリゴンを描画したい
- 描画するために何が必要なのかを知りたい
- 入力アセンブラ(IA)とは何か知りたい
サンプル
サンプルはここからダウンロードでき、環境については以下の内容となっています。
開発環境
VSのバージョン |
VisualStudio 2019 |
DirectXのバージョン |
DirectX11 |
描画構造
下の図は簡単なポリゴンを描画する場合の描画構造です。
複数のバッファから、入力アセンブラやシェーダに情報が送られています。
送信先の種類
バッファの送信先は以下の二種類に分かれています。
入力アセンブラ(IA)
入力アセンブラとはレンダリングパイプラインのスタート地点のことです。
DirectXではIA(InputAssembler)という形で関数名などに使われています。
各バッファが入力アセンブラに必要な情報を送信することで、
シェーダを動かすための情報が手に入り、描画を始めることができます。
送信は入力レイアウトやインデックスバッファは入力アセンブラに情報を送信しますが、
頂点バッファは入力スロットと呼ばれる場所に送信します。
入力スロット
入力スロットとは複数の頂点構成のバッファに対応できるようにするための
頂点構成の保存場所のことです。
例えば「座標とカラー」と「座標と法線とUV座標」の頂点情報を持つ
頂点バッファがあるとします。
この時に入力スロットがなく、一つしか頂点バッファを設定できないとしたら、
未使用の情報が含まれまれることを覚悟して「座標とカラー、法線、UV座標」を
持つ頂点バッファを用意するなどの対応が必要です。
ですが、入力スロットを利用すれば「座標とカラー」はスロット0番、
「座標と法線とUV座標」はスロット1番と設定して情報を送信することで、
異なる頂点バッファであったとしても柔軟に対応できます。
ただし、入力スロットは受け入れられる頂点構成の設定が行われるので
適当にスロットを指定しても描画が成立する保証はありません。
この入力スロットの頂点構成は入力レイアウトで行います。
シェーダ
DirectX11の各シェーダでは以下の方法で、情報の取得をできるようになっています。
シェーダ関数の引数
各シェーダは必ずエントリポイントの関数を用意することになっており、
その関数の引数で、一つ前のシェーダや入力アセンブラ、ラスタライザから
情報を送ってもらえるようになっています。
コンスタントバッファ
コンスタントバッファの詳しい説明の後ほど各バッファの説明で行いますが、
エントリポイント関数の引数では取得できない情報をコンスタントバッファを
通すことで取得できるようになります。
情報送るために用意すべきもの
ポリゴンを描画するために必要な情報を入力アセンブラや各シェーダに送るためには
以下のものを用意する必要があります。
- 頂点バッファ
- インデックスバッファ
- コンスタントバッファ
- 入力レイアウト
これらの各情報の説明をする前に、各バッファを作成する上で
共通して使用する構造体や関数がありますので、そちらの説明を先に行います。
汎用バッファ
次の項目から頂点バッファやインデックスバッファ、コンスタントバッファを
作成しますが、これらのバッファは全て同じ構造体、関数を使用して行います。
バッファはデータを保持するための入れ物なので、そのバッファがどのような情報を
保持しているかを設定することで、複数のバッファに対応できるようにしてあります。
バッファ情報の設定を保存する構造体が「D3D11_BUFFER_DESC」と
「D3D11_SUBRESOURCE_DATA」で、バッファの作成の関数がID3D11Deviceの
「CreateBuffer関数」です。
D3D11_BUFFER_DESC
D3D11_BUFFER_DESC構造体はバッファの使われ方の設定をする構造体です。
ByteWidthとBindFlags、StructureByteStrideはバッファごとに可変する情報で、
その他の情報はあまり変わることはありません。
※StructureByteStrideも0を指定すれば値を変更せずに対応できます。
D3D11_SUBRESOURCE_DATA
D3D11_SUBRESOURCE_DATA構造体は作成するバッファの初期化データを保存する構造体です。
この構造体で大切な項目はpSysMemで、初期化するデータを指定します。
頂点バッファなら頂点構造体の配列、インデックスバッファなら頂点バッファの
頂点番号の配列などを指定します。
CreateBuffer
CreateBuffer関数はD3D11_BUFFER_DESCとD3D11_SUBRESOURCE_DATAの情報をもとにして
バッファを作成する関数です。
頂点バッファ
頂点バッファはポリゴン描画の大元になる情報で、
ポリゴンを描画するための頂点情報のバッファです。
頂点情報には座標や法線、頂点カラーなどの情報を保存しなければいけないので
専用の構造体を用意して対応します。
※基本的には頂点の座標やカラーなどは3Dモデルを使用して用意する情報です。
その為、今回のサンプルようにソース上で用意することは、ほとんどありません。
// 頂点情報
struct CustomVertex {
float pos[ 3 ];
float col[ 4 ];
};
今回描画するポリゴンの頂点情報は座標とカラーだけです。
この頂点情報を三角形一つ分の三頂点だけ用意します。
CustomVertex g_VertexList01[] {
{ { -0.2f, 0.2f, 0.2f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.2f, -0.2f, 0.2f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.2f, -0.2f, 0.2f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};
用意した頂点情報を使用して頂点バッファを作成します。
まず、D3D11_BUFFER_DESCで作成する頂点バッファの設定をします。
// 頂点バッファとして作成するための情報設定
D3D11_BUFFER_DESC vertex_buffer_desc;
vertex_buffer_desc.ByteWidth = sizeof( CustomVertex ) * 3; // バッファのサイズ(頂点数)
vertex_buffer_desc.Usage = D3D11_USAGE_DEFAULT; // 使用方法
vertex_buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // バッファの種類(頂点バッファ)
vertex_buffer_desc.CPUAccessFlags = 0; // CPUアクセス設定
vertex_buffer_desc.MiscFlags = 0; // その他のオプション
vertex_buffer_desc.StructureByteStride = sizeof(CustomVertex); // 構造体のサイズ
次に、D3D11_SUBRESOURCE_DATAで頂点バッファの初期値の指定をします。
// バッファの初期値指定
D3D11_SUBRESOURCE_DATA vertex_init_data;
// 頂点バッファの初期値
vertex_init_data.pSysMem = model_type == Type::Test01 ? g_VertexList01 : g_VertexList02;
// 頂点バッファでは使用しない
vertex_init_data.SysMemPitch = 0;
vertex_init_data.SysMemSlicePitch = 0;
最後に上の二つの構造体を使ってCreateBufferで頂点バッファを作成します。
// 頂点バッファ作成
if (FAILED(device->CreateBuffer(&vertex_buffer_desc, &vertex_init_data, &m_VertexBuffer)))
{
// 作成失敗
return false;
}
インデックスバッファ
インデックスバッファは効率的にポリゴンを描画するために使用するバッファで、
頂点バッファの頂点番号をポリゴン単位で保存しています。
※インデックスバッファも頂点バッファと同様に3Dモデルを使用して用意する情報です。
その為、今回のサンプルようにソース上で用意することは、ほとんどありません。
// インデックス情報
UWORD g_IndexList01[]{
0, 1, 2
};
上の配列は頂点バッファの頂点番号「0 1 2」で
一つのポリゴンを作成することを意味しています。
この配列を使用してインデックスバッファを作成します。
まず、D3D11_BUFFER_DESCで作成するインデックスバッファの設定をします。
// インデックスバッファとして作成するための情報設定
D3D11_BUFFER_DESC index_buffer_desc;
index_buffer_desc.ByteWidth = sizeof(UWORD) * 3; // バッファのサイズ(頂点数)
index_buffer_desc.Usage = D3D11_USAGE_DEFAULT; // 使用方法
index_buffer_desc.BindFlags = D3D11_BIND_INDEX_BUFFER; // バッファの種類(インデックスバッファ)
index_buffer_desc.CPUAccessFlags = 0; // CPUアクセス設定
index_buffer_desc.MiscFlags = 0; // その他のオプション
index_buffer_desc.StructureByteStride = 0; // 構造体ではないので0
次に、D3D11_SUBRESOURCE_DATAでインデックスバッファの初期値の指定をします。
// インデックスバッファの初期値指定
D3D11_SUBRESOURCE_DATA index_init_data;
// バッファの初期値設定
index_init_data.pSysMem = model_type == Type::Test01 ? g_IndexList01 : g_IndexList02;
// インデックスバッファでは使用しない
index_init_data.SysMemPitch = 0;
index_init_data.SysMemSlicePitch = 0;
最後に上の二つの構造体を使ってCreateBufferで頂点バッファを作成します。
// インデックスバッファ作成
if (FAILED(device->CreateBuffer(&index_buffer_desc, &index_init_data, &m_IndexBuffer)))
{
// 作成失敗
return false;
}
コンスタントバッファ
コンスタントバッファとは頂点バッファと別でパイプラインに情報を
伝えることができるバッファです。
描画するオブジェクトのワールド座標やライトの情報などは
このバッファからパイプラインに送信します。
コンスタントバッファは描画時に設定を行うため、
頂点、インデックスバッファのようにモデル単位で必要としません。
その為、コンスタントバッファはDirectGraphics側で用意しています。
作成は、頂点、インデックスバッファ同様にD3D11_BUFFER_DESCで
バッファの設定をします。
// コンスタントバッファとして作成するための情報設定
D3D11_BUFFER_DESC contstat_buffer_desc;
contstat_buffer_desc.ByteWidth = sizeof(ConstantBuffer); // バッファのサイズ
contstat_buffer_desc.Usage = D3D11_USAGE_DEFAULT; // 使用方法
contstat_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; // バッファの種類(コンスタントバッファ)
contstat_buffer_desc.CPUAccessFlags = 0; // CPUアクセス設定
contstat_buffer_desc.MiscFlags = 0; // その他のオプション
contstat_buffer_desc.StructureByteStride = 0; // 構造体サイズ(行列を使うが今回は0でも動作することを実証する)
設定が完了したらCreateBufferで作成を行います。
コンスタントバッファではバッファを特定情報で初期化する必要がないので、
D3D11_SUBRESOURCE_DATAは使用しません。
// コンスタントバッファ作成
if (FAILED(m_Device->CreateBuffer(&contstat_buffer_desc, nullptr, &m_ConstantBuffer)))
{
// 作成失敗
return false;
}
入力レイアウト
入力レイアウトとはレンダリングパイプラインの入力アセンブラに
送信する頂点情報のレイアウトのことです。
CPU側で用意した頂点上の構成はGPU側には伝わっていません。
その為、描画で使用する頂点には座標は座標や法線が含まれていることを
GPU側に伝える必要があります。
この頂点情報の構成をGPU側に伝える役割を担っているのが入力レイアウトです。
基本的には頂点バッファで使用している頂点構成をそのまま使用します。
入力レイアウト情報の用意
入力レイアウトの作成は「D3D11_INPUT_ELEMENT_DESC構造体」を使って、
頂点情報の設定をすることから始めます。
// 入力レイアウト
D3D11_INPUT_ELEMENT_DESC g_VertexDesc[]{
{
"POSITION", // セマンティック名
0, // セマンティック番号
DXGI_FORMAT_R32G32B32_FLOAT, // フォーマット
0, // 入力スロット番号
0, // 要素から先頭までのオフセット値
D3D11_INPUT_PER_VERTEX_DATA, // 入力データの種類
0 // 繰り返し回数(頂点データの時は0)
},
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
D3D11_INPUT_ELEMENT_DESCに情報を設定することで、入力レイアウトはシェーダ側の
入力シグネチャとの繋がりを作り、シェーダ側で頂点情報を取得することができます。
セマンティック名
セマンティック名はHLSL側の入力シグネチャの名前と一致させることで
送信した頂点情報を受信させることができます。
例えば今回の設定で「POSITION」「COLOR」とありますが、
以下のように、同じ名前がHLSL側にもあります。
// VertexShaderに送られてくるデータ構造
struct VS_IN
{
float4 pos : POSITION0;
float4 col : COLOR0;
};
入力レイアウトのPOSITIONの情報がHLSL側のPOSITIONに送信されるということです。
セマンティック番号
セマンティック番号はセマンティック名が同じ場合でも
識別できるようにするための番号です。
先ほどのコードでシグネチャの後ろに「0」が設定されていましたが、
これがセマンティック番号です。
同じPOSITIONでもセマンティック番号を変更することで
HLSL側で別の情報だと認識することができます。
フォーマット
フォーマットはデータの型やバイト数の設定を行います。
例えばPOSITIONとCOLORは「DXGI_FORMAT_R32G32B32_FLOAT」を設定していますが、
これは「RGBの各要素を32ビット使えるfloat型」という意味です。
つまり、型やサイズで考えるとfloat[3]と同じです。
入力スロット番号
入力スロット番号は、どの入力スロットに対して入力レイアウトを
反映させるかを指定します。
番号指定された入力スロットは、この入力レイアウトの構成で
頂点バッファの情報を入力アセンブラに送信します。
要素の先頭までのオフセット
要素の先頭までのオフセットは各データが配列の先頭から
何バイト離れているかということです。
例えば、POSITIONは配列の先頭なので、オフセットは0です。
次のCOLORはPOSITIONがDXGI_FORMAT_R32G32B32_FLOATを指定しているので
float * 3で12バイトずれた位置からデータが開始されることになります。
この「12バイト」がオフセット値です。
全てのデータに対してオフセットの計算をするのは大変なので、
二つ目のデータ移行は「D3D11_APPEND_ALIGNED_ELEMENT」を指定します。
この定数を指定するとオフセット値を設定するのと同じ効果があります。
入力データの種類
入力データには頂点データとインスタンスデータがあります。
今回は頂点データを送信するので、「D3D11_INPUT_PER_VERTEX_DATA」を
設定しています。
インスタンス毎の繰り返し回数
入力データで「D3D11_INPUT_PER_INSTANCE_DATA」を指定した場合に
この項目は意味を持ちますがD3D11_INPUT_PER_VERTEX_DATAだったら、
「0」指定で問題ありません。
入力レイアウト作成
入力レイアウト用の情報が用意出来たらID3D11Deviceの
「CreateInputLayout関数」を使って入力レイアウトを作成します。
//レイアウト作成
if (FAILED(device->CreateInputLayout(
g_VertexDesc, // 入力レイアウトの構成
ARRAYSIZE(g_VertexDesc), // 構成の要素数
vertex_shader->GetData(), // 頂点シェーダデータ
vertex_shader->GetSize(), // 頂点シェーダのサイズ
&m_InputLayout))) // 作成した入力レイアウトの保存
{
// 作成失敗
return false;
}
これで準備処理の実装は終了です。
描画処理の実装はこちらで書いているので、興味がある方はご覧ください。