DX Work with shaders and shader resources - yoshimune/LearningDirectX11 GitHub Wiki
シェーダーとシェーダーリソースが開発中のDirectXゲーム中でがどうやって動いているかを学ぶ時が来ました。グラフィックスデバイスとリソースのセットアップの方法は見ています。そしておそらく、既にこのパイプラインの変更を開始しています。よって今から、ピクセルシェーダと頂点シェーダを見ていきましょう。
もしシェーダー言語についてよく知らない場合、quick discussionは並んでいます。シェーダーは小さく、低レベルプログラムで、グラフィックスパイプラインの特定のステージでコンパイルと実行が行われます。特徴は、浮動少数の演算がとても高速に動作することです。もっとも一般的なシェーダープログラムは、以下のとおりです。
- Vertex Shader
- シーン上のそれぞれの頂点毎に実行します。このシェーダーは呼び出し元のアプリケーションによって提供される頂点バッファ要素で実行されます。ピクセル位置にラスタライズされる4要素の位置ベクトルを最小限にします。
- Pixel Shader
- レンダーターゲットの各ピクセルごとに実行されます。このシェーダーは、事前のシェーダーステージ(単純なパイプラインの場合は、頂点シェーダ)からラスタライズされた座標を受け取ります。また、レンダーターゲットに書き込むピクセルのカラー(4要素の値)を返します。
この例では、ジオメトリを描画するだけのとても基本的な頂点・ピクセルシェーダと、基本的なライト計算を行うもう少し複雑なシェーダを含みます。
シェーダープログラムは_Microsoft High Level Shader Language(HLSL)_で書かれています。HLSL構文はC言語に似ています。しかし、ポインタはありません。シェーダープログラムはとてもコンパクト・効果的でなくてはなりません。もしシェーダーコンパイルにが多すぎる指示があった場合、実行されず、エラーが帰ってきます(許可されている正確な数はDirect3D feature levelを参照)。
Direct3D内では、シェーダーは実行時にコンパイルされません。シェーダーはプログラムがコンパイルされたときにコンパイルされます。アプリケーションがVisualStudioでコンパイルされたとき、HLSLはCSO(.cso)ファイルにコンパイルされます。csoファイル描画前にはアプリケーションによってロードされ、GPUメモリ上に配置されなくてはなりません。パッケージがしたときにこれらのCSOファイルを含んでいることを確認してください。これらは単にメッシュやテクスチャのような資産です。
Understand HLSL semantics
話を進める前に、HLSLセマンティクスについて議論することが重要です。なぜなら、セマンティクスは通常新しくDirect3Dを開発する人が混乱するポイントです。HLSLセマンティクスは、アプリケーションとシェーダープログラム間での値を受け渡しの識別をする文字列です。しかし、セマンティクスは様々な値(文字列)をとることができます。ベスト・プラクティスはPOSITION
やCOLOR
のような使い方を支持する文字列を使うことです。コンスタントバッファやインプットレイアウトを構築するときにこれらのセマンティクスを割り当てます。また、同じような値を見分けることに利用するために、0-7の番号をセマンティクスに追加できます。例えば、COLOR0
、COLOR3
、COLOR2
...
"SV_"という接頭辞がついたセマンティクスは、シェーダープログラムによって書かれたシステム値セマンティクスです。ゲーム自身(CPUで動いている)は変更できません。典型的には、これらのセマンティクスはグラフィックスパイプライン内の別のシェーダーステージから入力または出力された値を含みます。もしくはGPUから完全に生成されます。
更に、SV_
セマンティクスは、シェーダーステージから入力または出力指定に使われたときの別の動作をもっています。例えば、SV_POSITION
(output)は頂点データステージで変換された頂点データを含みます。また、SV_POSITION
(input)はGPUによってラスタライズステージで保管されたピクセルの位置を含みます。
HLSLのセマンティクスを少し紹介します。
- 頂点バッファの
POSITION
。SV_POSITION
はピクセル位置をピクセルシェーダーに提供し、ゲームプログラムによって書き換えできません。 NORMAL
は頂点シェーダーから法線情報を提供します。TEXCOORD
はシェーダーにテクスチャUV座標を提供します。COLOR
はシェーダーにRGBA情報を供給します。これは座標情報も同様に扱い、ラスタライゼーション中の補間値を含みます。セマンティクスはカラーデータの識別を簡単に手助けします。SV_Target
はピクセルシェーダーからターゲットテクスチャ、または別のピクセルバッファに書き込みます
Read from the constant buffers
いくつかのシェーダーは、コンスタントバッファがアタッチされている場合、コンスタントバッファから読まれます。この例では、頂点シェーダーのみコンスタントバッファに割り当てられています。
コンスタントバッファは2つの場所で宣言されています。C++コードと、対応するHLSLファイルです。
以下は、コンスタントバッファストラクトをC++でどう宣言するかという例です。
typedef struct _constantBufferStruct {
DirectX::XMFLOAT4X4 world;
DirectX::XMFLOAT4X4 view;
DirectX::XMFLOAT4X4 projection;
} ConstantBufferStruct;
C++でコンスタントバッファ構造体を宣言したとき、確実に全てのデータが16byte境界に整列している必要があります。これを実現する最も簡単な方法は、XMFLOAT4やXMFLOAT4X4DirectXMathを使用することです。static assertでバッファの整列ミスを防ぐことができます。
// Assert that the constant buffer remains 16-byte aligned.
static_assert((sizeof(ConstantBufferStruct) % 16) == 0, "Constant Buffer size must be 16-byte aligned");
この行のコードはコンパイル時に、ConstantBufferStructが16byte長になっていない場合にエラーを出します。コンスタントバッファの割当とパッキングに関しての詳細はPackingまたはRules for Constant Variablesを参照してください。
以下はHLSLシェーダー内でコンスタントバッファをどうやって宣言するかという例です。
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mWorld; // world matrix for object
matrix View; // view matrix
matrix Projection; // projection matrix
};
全てのバッファ(constant, texture, sampler, 他)は、GPUがアクセスできるように、レジスタ定義を持っていなくてはなりません。それぞれのシェーダーステージは15のコンスタントバッファを許可し、それぞれのバッファは4096の定数値を保持することができます。register-usage 宣言の構文は以下のとおりです。
- c*#*: コンスタントバッファ(cbuffer)
- t*#*: テクスチャバッファ(tbuffer)
- s*#*: サンプラーバッファ(サンプラーはテクスチャリソースからテクセルを取得する動作の定義)
例えば、HLSLのピクセルシェーダーはテクスチャとサンプラーをインプットとして持っているかもしれません。
Texture2D simpleTexture : register(t0);
SamplerState simpleSampler : register(s0);
Direct3D 11 での頂点データの通常のフォーマットはありません。代わりに、自身の頂点データレイアウトを記述して定義します。データフィールドはID3D11 INPUT ELEMENT DESCを使って定義されます。ここでは、先行する構造体と同じ頂点フォーマットで記述された単純なインプットレイアウトを見せます。
D3D11_INPUT_ELEMENT_DESC iaDesc [] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
hr = device->CreateInputLayout(
iaDesc,
ARRAYSIZE(iaDesc),
bytes,
bytesRead,
&m_pInputLayout
);
もし、例示のコードの変更時に頂点フォーマットにデータを追加する場合は、インプットレイアウトも合わせて更新してください。そうしないとシェーダーはそれを解釈できません。以下のように変更できます。
typedef struct _vertexPositionColorTangent
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT3 tangent;
} VertexPositionColorTangent;
このケースでは、インプットレイアウト定義を以下のように変更します。
D3D11_INPUT_ELEMENT_DESC iaDescExtended[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
hr = device->CreateInputLayout(
iaDesc,
ARRAYSIZE(iaDesc),
bytes,
bytesRead,
&m_pInputLayoutExtended
);
それぞれのインプットレイアウト要素定義はPOSITION
やNORMAL
といった文字列のプレフィックスです。これはこのトピックの前の方で解説したセマンティクスです。これは、GPUが頂点を処理するときにその要素を識別するのに役立つハンドルのようなものです。頂点要素に共通の意味のある名前を選択します。
コンスタントバッファとともに、頂点シェーダーは頂点要素から定義された対応するバッファを持ちます。(そのため、インプットレイアウト作成時に頂点シェーダーリソースに参照を提供します。Direct3Dは頂点ごとのデータレイアウトをシェーダー入力構造体とともに検証します)。セマンティクスがインプットレイアウト定義とこのHLSLバッファ宣言との間でどうやって一致するかに注意してください。しかし、COLOR
は0個の追加です。もしCOLOR
要素をひとつだけの場合は0を追加する必要はありません。しかし、将来的により多くのカラー要素を追加するというケースに備えて、追加することをお勧めします。
struct VS_INPUT
{
float3 vPos : POSITION;
float3 vColor : COLOR0;
};
Pass data between shaders
シェーダーはインプットタイプと変換されるアウトプットタイプをメイン関数から取得します。頂点シェーダーは前のセクションで定義します。インプットタイプはVS_INPUT構造体です。そして適合するインプットレイアウトとC++構造体を定義します。構造体の配列はCreateCubeメソッド内で頂点バッファの作成に使われます。
頂点シェーダーはPS_INPUT構造体を返します。これは最小でも4要素(float4)の最終的な頂点位置情報を含まなくてはなりません。この位置情報はSV_POSITION
と宣言されたシステムバリューセマンティクスを持っていなくてはなりません。GPUは次の描画処理に必要なデータを持っている必要があるためです。頂点シェーダーアウトプットとピクセルシェーダインプットは1:1対応ではありません。頂点シェーダーは頂点毎に与えられた一つの構造体を返します。しかし、ピクセルシェーダーはピクセルごとに実行されます。そのため、最初に頂点ごとのデータがラスタライゼーションステージに渡されます。このステージはピクセルが描画されたジオメトリをカバーしているかを決定し、頂点データ毎にそれぞれのピクセルを補間計算します。そのあと、ピクセル毎にピクセルシェーダを呼びます。補間は、ラスタライジングが値を出よくしたとき、デフォルトで動作します。そして、特に出力ベクトルデータ(光ベクトル、頂点ごとの法線と接戦など)の正しい処理には不可欠です。
struct PS_INPUT
{
float4 Position : SV_POSITION; // interpolated vertex position (system value)
float4 Color : COLOR0; // interpolated diffuse color
};
Review the vertex shader
頂点シェーダーの例は、とてもシンプルです。頂点情報(位置とカラー)を受け取り、位置をモデル座標から投影座標へ変換し、ラスタライザへ返します。カラー値は位置情報を元に補間されていること、頂点シェーダーが色の計算を実行しなくても。別の値がそれぞれのピクセルに提供されていることに注意してください。
VS_OUTPUT main(VS_INPUT input) // main is the default function name
{
VS_OUTPUT Output;
float4 pos = float4(input.vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, mWorld);
pos = mul(pos, View);
pos = mul(pos, Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(input.vColor, 1.0f);
return Output;
}