頂点シェーダとピクセルシェーダ
プログラム上でシェーダを扱う機能は実装できました。
次は実際に使用するシェーダファイルを作成していきましょう。
今回は単純に頂点の値を入力してそのままピクセルシェーダで表示するだけの機能にします。
Shader.hlsl
// 頂点シェーダ入力情報
struct VS_INPUT
{
float3 Position : POSITION;
float4 Color : COLOR;
};
// 頂点シェーダ出力情報
struct VS_OUTPUT
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
};
// 頂点シェーダ
void VS(in VS_INPUT In, out VS_OUTPUT Out)
{
Out.Position.xyz = In.Position.xyz;
Out.Position.w = 1.0f;
Out.Color = In.Color;
}
// ピクセルシェーダ入力情報
typedef VS_OUTPUT PS_INPUT;
// ピクセルシェーダ
void PS(in PS_INPUT In, out float4 OutColor : SV_Target0)
{
OutColor = In.Color;
}
内容は単純です。
VS関数
これが頂点シェーダの本体となります。
先ずVS_INPUTで座標と頂点カラーを受け取ります。
そして受け取った内容をそのままVS_OUTPUTに渡して出力しています。
引数の頭に in、outとついていますが、これで入出力の内容を設定しています。
outは戻り値で指定することもできます。
VS_INPUTの変数の後ろに記述している POSITION や COLOR については任意の文字を指定できます。
次の項目で記述しますが、この部分は頂点バッファのどの値を関連付るかという設定をC++側で行います。
VS_OUTPUTの変数の後ろに記述している SV_POSITION や COLOR0 は使用できる名前が決まっています。
一般的に座標はSV_POSITION、カラーはCOLOR[n]、UV値はTEXCOORD[n]となります。
他にもありますが、詳しくは公式ドキュメントを参考にしてください。
PS関数
こちらはピクセルシェーダの本体です。
VS_OUTPUTで出力された内容と同じものが入力で入ってきます。
なのでVS_OUTPUTをtypedefでPS_INPUTとしています。
ピクセルシェーダの出力はSV_Targetを指定する必要があります。
今回はSV_Target0で 0 となっていますが、複数のレンダーターゲットに同時に出力する場合は1, 2, 3と末尾を変更します。
頂点入力レイアウト
これで描画に最低限必要なシェーダが実装出来ました。
ただ頂点シェーダを扱うには頂点バッファと頂点シェーダの入力情報を関連付る必要があります。
入力情報の関連付にはID3D11InputLayoutを使用します。
InputLayout.h
// InputLayoutクラス
class InputLayout
{
public:
InputLayout(void) : m_pInputLayout(NULL) {}
~InputLayout(void) { SafeRelease(m_pInputLayout); }
// 初期化
bool Initialize(DirectX11& directX, VertexShader& shader, D3D11_INPUT_ELEMENT_DESC* pElements, UINT num);
// 終了処理
void Finalize(void);
public:
// 頂点入力レイアウトの取得
ID3D11InputLayout* GetInputLayout(void) { return m_pInputLayout; }
private:
ID3D11InputLayout* m_pInputLayout; // 頂点入力レイアウト
};
ID3D11InputLayoutではD3D11_INPUT_ELEMENT_DESCとシェーダコードから入力情報をひもづけます。
InputLayout.cpp
#include "InputLayout.h"
// 初期化
bool InputLayout::Initialize(DirectX11& directX, VertexShader& shader, D3D11_INPUT_ELEMENT_DESC* pElements, UINT num)
{
auto pDevice = directX.GetDevice();
auto pCode = shader.GetShaderCode();
// 入力レイアウトの生成
if (FAILED(pDevice->CreateInputLayout(
pElements,
num,
pCode->GetBufferPointer(),
pCode->GetBufferSize(),
&m_pInputLayout)))
{
return false;
}
return true;
}
// 終了処理
void InputLayout::Finalize(void)
{
SafeRelease(m_pInputLayout);
}
頂点シェーダへの入力情報はID3DDevice::CreateInputLayoutで生成します。
ここでD3D11_INPUT_ELEMENT_DESCを使って頂点シェーダのVS_INPUTに指定する POSITION や COLOR といったSemantic(意味付け)を指定します。
今回は座標とカラーのみなので下記のような設定を渡します。
頂点入力情報の設定
// 頂点データ
struct Vertex
{
float x, y, z;
float r, g, b, a;
};
// Vertexに対応する入力設定
D3D11_INPUT_ELEMENT_DESC elements[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 4 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
これで POSITION と COLOR にそれぞれ値の紐付けが行われます。
D3D11_INPUT_ELEMENT_DESC
| SemanticName | シェーダの入力情報に意味付けを行います。 変数の後に記述する内容と一致している場合、この設定の値が紐付きます。 |
| SemanticIndex | 同一のSemantic名を複数使用する場合はここで番号を指定できます。 SemanticNameをPOSITIONにした際に0, 1のIndexを使用する場合は次が使用できます。 ・POSITION0 ・POSITION1 |
| Format | 入力する変数の型を指定できます。 渡すバッファのサイズに合わせてドキュメントを参考に指定してください。 |
| InputSlot | 使用する頂点バッファのスロットを指定できます。 これにより複数の頂点バッファを使用して描画することもできます。 |
| AlignedByteOffset; | 関連付ける値が頂点内のどの位置にあるかオフセットを指定します。 byte単位での指定となります。 |
| InputSlotClass | 頂点バッファの1要素を進める条件を指定します。 基本的にD3D11_INPUT_PER_VERTEX_DATAで問題ありません。 |
| InstanceDataStepRate | D3D11_INPUT_PER_VERTEX_DATAの場合は 0 を指定します。 D3D11_INPUT_PER_INSTANCE_DATAの場合は描画するインスタンス数を指定します。 |
色々とややこしい設定もありますが、今のところは複数頂点バッファやインスタンス描画は使用しませんので最低限の設定だけで問題ありません。