DirectX11シェーダー入門

シェーダーを使いたい、勉強したいと思っても作成する環境が必要です。
なのでVisualStudio2017とDirectX11でシェーダーをコンパイルして実行結果の確認ができる最小限の環境を作成してみました。シェーダー入門とありますがシェーダーの書き方ではなく、シェーダーを書くまでに必要な設定などについての説明です。
ソースコードを公開していますので参考にしてください。


サンプルソースコード

“Start DirectX11 Shader” をダウンロード StartDirectX11Shader-1.zip – 533 回のダウンロード – 29 KB


このサンプルに沿って説明していきます。

VisualStudio2017、DirectX11の初期化、画面クリアについては
VisualStudio2017(Windows10 DirectX OpenGL)で始める3Dプログラミング
を見てください。


シェーダのコンパイル

シェーダーのソースコードをコンパイルしてDirectX11で読み込める形式のバイナリデータに変換します。プログラム実行時にコンパイル可能ですが、通常は事前にシェーダー用コンパイラで変換します。
VisualStudioには、シェーダーをコンパイルする機能があるため、C++のコンパイルと同時にビルドすることができます。

VisulaStudioでのシェーダーコンパイル

ソースコードのプロパティー「項目の種類」を「HLSLコンパイラ」に変更するとシェーダーのコンパイルを行います。特定の拡張子(*.fx *.hlsl *.vsh *.pshなど)をファイルをプロジェクトに追加すると自動的に設定されます。設定を変更するとプロパティーに「HLSLコンパイラ」の項目が追加され、コンパイルオプションや出力場所などの設定ができるようになります。

主な設定項目
エントリポイント名 シェーダーのメイン関数の名前を入力します。
シェーダーの種類 「頂点シェーダー」や「ピクセルシェーダー」などを選択します。
シェーダーモデル デフォルトのShader Model 4 Level 9_3のままで。新しい機能を使用する場合は対応したモデルに変更します。
オブジェクトファイル名 バイナリデータの出力場所を指定します。拡張子は*.cso。

シェーダーソースコード

頂点シェーダーとピクセルシェーダーのソースコードを作成、VisualStusioに追加します。
シェーダーの内容についての詳細は省略します。頂点シェーダーで3次元の座標変換、ピクセルシェーダーではテクスチャ処理を行っています。

頂点シェーダー vshader.vsh
cbuffer ConstBuff : register(b0)
{
  matrix mtxProj;
  matrix mtxView;
  matrix mtxWorld;
  float4 Diffuse;
};

struct VS_INPUT
{
  float3 Pos : POSITION;
  float2 Tex : TEXCOORD;
};

struct PS_INPUT
{
  float4 Pos : SV_POSITION;
  float2 Tex : TEXCOORD;
};

PS_INPUT vsMain(VS_INPUT input)
{
  PS_INPUT output = (PS_INPUT)0;
  output.Pos = mul(float4(input.Pos,1), mtxWorld);
  output.Pos = mul(output.Pos, mtxView);
  output.Pos = mul(output.Pos, mtxProj);
  output.Tex = input.Tex;
  return output;
}
ピクセルシェーダー pshader.psh
Texture2D txDiffuse : register(t0);
SamplerState samLinear : register(s0);

cbuffer ConstBuff : register(b0)
{
  matrix mtxProj;
  matrix mtxView;
  matrix mtxWorld;
  float4 Diffuse;
};

struct PS_INPUT
{
  float4 Pos : SV_POSITION;
  float2 Tex : TEXCOORD;
};

float4 psMain(PS_INPUT input) : SV_Target
{
  return txDiffuse.Sample(samLinear, input.Tex)*Diffuse;
}

使用しているライブラリについて

ここから説明するソースコードで使用しているライブラリです。

ComPtr

COMインターフェイスの参照カウンタ管理を行ってくれるComPtrテンプレートクラス(スマートポインタ)を使用しています。DirectX11のインターフェイスクラス(ID3D11Device*など)は、COMインターフェイスから派生したクラスなので、ComPtrを使用できます。

DirectXMath

また、行列やベクトル計算にはDirectXMathというライブラリを使用しています。行列とベクトルのデータ型はXMMATRIX、XMVECTORで専用の関数を使用してベクトル計算を高速に行います。WindowsSDKに含まれています。

DirectXTex

DirectX11でのテクスチャ作成を支援するためのライブラリです。ここから一般的な画像ファイルからテクスチャ作成するソースコード(WICTextureLoader.h/cpp)を抜き出して使用しています。


シェーダー作成

コンパイルで生成したバイナリファイル(*.cso)から、DirectX11のシェーダーインターフェイス(ID3D11VertexShader ID3D11PixelShader)を作成します。

  // ComPtr<ID3D11Device> pDevice;
  // ComPtr<ID3D11VertexShader> pVertexShader;
  // ComPtr<ID3D11PixelShader> pPixelShader;
  // const void* vcode = 頂点シェーダバイナリデータ
  // int vcode_size = 頂点シェーダバイナリデータサイズ
  // const void* pcode = ピクセルシェーダバイナリデータ
  // int pcode_size = ピクセルシェーダバイナリデータサイズ
  pDevice->CreateVertexShader(vcode, vcode_size, NULL, &pVertexShader);
  pDevice->CreatePixelShader(pcode, pcode_size, NULL, &pPixelShader);

頂点バッファと入力レイアウト

シェーダーに位置や法線などを入力するために、専用のバッファ(ID3D11Buffer)を作成しデータを書き込みます。また、書き込んだデータ構造を定義するために入力レイアウト(ID3D11InputLayout)を作成し、バッファとともに指定します。

頂点バッファの作成

D3D11_BIND_VERTEX_BUFFERを指定してバッファ(ID3D11Buffer)を作成します。書き込むデータの情報も同時に渡します。

  //頂点データ構造体
  struct VertexData
  {
    FLOAT x, y, z;
    FLOAT tx,ty;
  };
	
  // 頂点バッファ作成
  VertexData vertices[3] = {
    { 0.0f,  0.5f, 0.0f,  0.5f,  0.0f },
    { 0.5f, -0.5f, 0.0f,  1.0f,  1.0f },
    { -0.5f, -0.5f, 0.0f, 0.0f,  1.0f },
  };

  D3D11_BUFFER_DESC bd;
  ZeroMemory(&bd, sizeof(bd));
  bd.Usage = D3D11_USAGE_DEFAULT;
  bd.ByteWidth = sizeof(VertexData) * 3;
  bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
  bd.CPUAccessFlags = 0;

  D3D11_SUBRESOURCE_DATA InitData;
  ZeroMemory(&InitData, sizeof(InitData));
  InitData.pSysMem = vertices;

  pDevice->CreateBuffer(&bd, &InitData, &pVertexBuffer);
入力レイアウト(ID3D11InputLayout)の作成

頂点データ構造の定義します。位置データfloat*3とテクスチャ座標float*2を1つの頂点としているためD3D11_INPUT_ELEMENT_DESCを以下のように定義し、入力レイアウトを作成します。それと頂点シェーダのバイナリデータも必要になります。

  // ComPtr<ID3D11InputLayout> pVertexLayout;
  // const void* vcode = 頂点シェーダバイナリデータ
  // int vcode_size = 頂点シェーダバイナリデータサイズ
	
  //頂点シェーダーでの入力
  //struct VS_INPUT{
  //  float4 Pos : POSITION;
  //  float2 Tex : TEXCOORD;
  //};

  // 入力レイアウト定義
  D3D11_INPUT_ELEMENT_DESC layout[] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  };
  UINT elem_num = ARRAYSIZE(layout);

  // 入力レイアウト作成
  pDevice->CreateInputLayout(layout, elem_num, vcode, vcode_size, &pVertexLayout);
セマンティクスと入力スロットについて

POSITIONがセマンティクス名、次の0がセマンティクスインデックスで、シェーダーとのデータの対応付けを行います。シェーダーのソースコードVS_INPUT構造体と見比べれば、すぐに理解できると思います。
セマンティクス名は自由に命名することができますが、数字は使用できません。セマンティクスインデックスはテクスチャ座標などを複数入力するときに使用します。
例えば、セマンティクス名TEXCOORD、インデックスを5とした場合、シェーダーにTEXCOORD5を記述します。ただし、インデックスが0の時は、省略可能でシェーダーにはTEXCOORDまたはTEXCOORD0と記述できます。
4つ目の数字(フォーマットの後)が入力スロット番号で、頂点バッファを複数指定した場合に、どのバッファを使用するか指定するための値です。


インデックスバッファ

D3D11_BIND_INDEX_BUFFERを指定し、バッファ(ID3D11Buffer)を作成します。
三角形ポリゴンなどを描画する際に、頂点データ配列のインデックス値で指定します。頂点バッファだけでも描画できますが、ポリゴン数が増えた場合インデックスバッファを使用すると重複した頂点データを削除できるため効率が良くなります。

  // ComPtr<ID3D11Buffer> pIndexBuffer;
 
  // vertices[0] vertices[1] vertices[2] で三角形をつくる
  UINT indices[3] = {0,1,2};
  
  D3D11_BUFFER_DESC bd;
  ZeroMemory(&bd, sizeof(bd));
  bd.Usage = D3D11_USAGE_DEFAULT;
  bd.ByteWidth = sizeof(int) * 3;
  bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
  bd.CPUAccessFlags = 0;
  
  D3D11_SUBRESOURCE_DATA InitData;
  ZeroMemory(&InitData, sizeof(InitData));
  InitData.pSysMem = indices;
  pDevice->CreateBuffer(&bd, &InitData, &pIndexBuffer);

定数バッファ(ConstantBuffer)

シェーダーに行列などの情報を渡すために、定数バッファ(ConstantBuffer)という専用バッファを使用します。

シェーダーでの定義
cbufferという専用の定義命令を使用します。定義の方法は構造体と同じように行います。定数バッファは複数定義でき、それぞれにスロット番号を割り振ります。register(b0)でスロット番号に0を割り振っています。省略した場合、自動的に割り振られますが番号の管理が面倒になるため番号固定に、また簡略化のため頂点シェーダーとピクセルシェーダーで同じ定数バッファを使用しています。本格的なプログラムを行う場合は、更新頻度や利用頻度を元に定数バッファを分割します。使用可能な定数バッファの数はD3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNTで定義されています。
cbuffer ConstBuff : register(b0)
{
  matrix mtxProj;
  matrix mtxView;
  matrix mtxWorld;
  float4 Diffuse;
};
定数バッファ作成

D3D11_BIND_CONSTANT_BUFFERを指定し、バッファ(ID3D11Buffer)を作成します。シェーダーでの定数バッファ定義と同じ構造を構造体を定義することで、シェーダーとのデータ受け渡しが簡単に行えます。

  //ComPtr<ID3D11Buffer> pCBuffer;

  //シェーダ定数バッファ
  struct ConstBuffer
  {
    XMMATRIX mtxProj;
    XMMATRIX mtxView;
    XMMATRIX mtxWorld;
    XMVECTOR Diffuse;
  };

  D3D11_BUFFER_DESC bd;
  ZeroMemory(&bd, sizeof(bd));
  bd.Usage = D3D11_USAGE_DEFAULT;
  bd.ByteWidth = sizeof(ConstBuffer);
  bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
  bd.CPUAccessFlags = 0;
  pDevice->CreateBuffer(&bd, nullptr, &pCBuffer);

定数バッファ更新

バッファ(ID3D11Buffer)を直接更新できないため、UpdateSubresource関数を使用します。DirectX(DirectXMath)は行優先、シェーダーは列優先行列となっているため、行列をシェーダーに渡す場合転置する必要があります。

  // ComPtr<ID3D11DeviceContext>	pImContext;

  ConstBuffer cbuff;
  
  // cbuffの内容書き換え	
  // 行列は転置する

  //定数バッファ内容更新
  pImContext->UpdateSubresource(pCBuffer.Get(), 0, NULL, &cbuff, 0, 0);

テクスチャとサンプラー

シェーダーが利用するテクスチャを作成します。DirectX11に画像ファイルからテクスチャを作成する機能がないため、開発支援ライブラリDirectXTexを使用しています。

テクスチャとシェーダーリソースビューの作成

DirectXTexのCreateWICTextureFromFile関数で画像ファイルからテクスチャ(ID3D11Resource)とシェーダーリソースビュー(ID3D11ShaderResourceView)を作成します。シェーダーからテクスチャにアクセスするために、画像フォーマットなどを定義したシェーダーリソースビューというオブジェクトを作成します。

  // ComPtr<ID3D11Resource> pTexture;
  // ComPtr<ID3D11ShaderResourceView> pShaderResView;
  DirectX::CreateWICTextureFromFile(pDevice.Get(), L"data\\image.png", &pTexture, &pShaderResView);
サンプラーの作成

テクスチャの色を取得する際の補間フィルタやミップマップなどを設定するためにサンプラー(ID3D11SamplerState)を作成します。

  // ComPtr<ID3D11SamplerState> pSampler;
  D3D11_SAMPLER_DESC sampDesc;
  ZeroMemory(&sampDesc, sizeof(sampDesc));
  sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
  sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
  sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
  sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
  sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
  sampDesc.MinLOD = 0;
  sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
  pDevice->CreateSamplerState(&sampDesc, &pSamplerLinear);
シェーダーでのテクスチャとサンプラー定義

定数バッファと同じようにスロット番号を使って、複数のテクスチャとサンプラーを定義できます。register(t0)とregister(s0)がスロット番号定義、指定しない場合自動的に割り振りされます。

  Texture2D txDiffuse : register(t0);
  SamplerState samLinear : register(s0);

レンダリングステート

シェーダー前後に実行される処理の動作を設定します。

ラスタライザステート(ID3D11RasterizerState)

頂点シェーダ実行後、ポリゴンをピクセルに変換するラスタライザの動作を設定します。デフォルトでは裏向きのポリゴンが表示されないため、その機能(カリング)を無効にしています。

  // ComPtr<ID3D11RasterizerState> pRsState;
  CD3D11_DEFAULT default_state;
  CD3D11_RASTERIZER_DESC rsdesc(default_state);
  rsdesc.CullMode = D3D11_CULL_NONE;
  pDevice->CreateRasterizerState(&rsdesc, &pRsState);
デプスステンシルステート(ID3D11DepthStencilState)

ピクセルシェーダー実行後のデプステストとステンシルテストに関連する設定を行います。デフォルト(テスト無効)設定を使用しています。

  // ComPtr<ID3D11DepthStencilState> pDsState;
  CD3D11_DEFAULT default_state;
  CD3D11_DEPTH_STENCIL_DESC dsdesc(default_state);
  pDevice->CreateDepthStencilState(&dsdesc, &pDsState);
ブレンドステート(ID3D11BlendState)
デプステストの後の最終的な色の出力に関する設定(煙のような半透明処理など)を行います。
ここではデフォルト設定(単純な上書き)を使用しています。

  // ComPtr<ID3D11BlendState> pBdState;
  CD3D11_DEFAULT default_state;
  CD3D11_BLEND_DESC bddesc(default_state);
  pDevice->CreateBlendState(&bddesc, &pBdState);

描画

これまで作成したものをすべて使って描画を実行します。DirectX11ではこれが描画の最小単位になります。

  // 頂点バッファ
  UINT vb_slot = 0;
  ID3D11Buffer* vb[1] = {pVertexBuffer.Get()};
  UINT stride[1] = { sizeof(VertexData)};
  UINT offset[1] = { 0 };
  pImContext->IASetVertexBuffers(vb_slot, 1, vb, stride, offset);

  // 入力レイアウト
  pImContext->IASetInputLayout(pVertexLayout.Get());

  // インデックスバッファ
  pImContext->IASetIndexBuffer(pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

  // プリミティブ形状(三角形リスト)
  pImContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

  // シェーダ
  pImContext->VSSetShader(pVertexShader.Get(), nullptr, 0);
  pImContext->PSSetShader(pPixelShader.Get(), nullptr, 0);

  // サンプラー
  UINT smp_slot = 0;
  ID3D11SamplerState* smp[1] = { pSampler.Get()};
  pImContext->PSSetSamplers(smp_slot, 1, smp);

  // シェーダーリソースビュー(テクスチャ)
  UINT srv_slot = 0;
  ID3D11ShaderResourceView* srv[1] = { pShaderResView.Get()};
  pImContext->PSSetShaderResources(srv_slot, 1, srv);

  //定数バッファ
  ConstBuffer cbuff;
	
  // プロジェクション行列
  FLOAT aspect = Viewport.Width / Viewport.Height;//アスペクト比
  FLOAT min_z = 0.01f;
  FLOAT max_z = 1000.0f;
  FLOAT fov = XM_PIDIV4;//画角
  cbuff.mtxProj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(fov, aspect, min_z, max_z));
	
  // カメラ行列
  XMVECTOR Eye = XMVectorSet(0.0f, 1.0f, -1.5f, 0.0f);
  XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
  XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
  cbuff.mtxView = XMMatrixTranspose(XMMatrixLookAtLH(Eye, At, Up));
	
  cbuff.mtxWorld = XMMatrixTranspose(XMMatrixRotationY(RotateY));
  cbuff.Diffuse = XMVectorSet(1.0f,1.0f,0.5f,1);
  // シェーダーでは行列を転置してから渡す

  // 定数バッファ内容更新
  pImContext->UpdateSubresource(pCBuffer.Get(), 0, NULL, &cbuff, 0, 0);

  // 定数バッファ
  UINT cb_slot = 0;
  ID3D11Buffer* cb[1] = {pCBuffer.Get()};
  pImContext->VSSetConstantBuffers(cb_slot, 1, cb);
  pImContext->PSSetConstantBuffers(cb_slot, 1, cb);

  // ラスタライザステート
  pImContext->RSSetState(pRsState.Get());

  // デプスステンシルステート
  pImContext->OMSetDepthStencilState(pDsState.Get(), 0);

  // ブレンドステート
  pImContext->OMSetBlendState(pBdState.Get(), NULL, 0xfffffff);

  // ポリゴン描画
  pImContext->DrawIndexed(3, 0, 0);

実行

シェーダーのバイナリデータや画像ファイルを読み込むため、作業ディレクトリ(カレントディレクトリ)の設定を忘れずに行います。
VSプロジェクトのプロパティ「デバッグ」⇒「作業ディレクトリ」で設定します。


シェーダバイナリコード読み込み問題

追記2017/5/31
標準関数のファイル読み込みにios::binaryを指定しないことが原因でした。文字列変換が働いて不正なバイナリコードになっていたようです。
シェーダーのコンパイルオプション「デバッグ情報あり」を「はい」にすると次のようなエラーが出てシェーダーの作成に失敗します。

D3D11 ERROR: ID3D11Device::CreateVertexShader: Vertex Shader is corrupt or in an unrecognized format. [ STATE_CREATION ERROR #166: CREATEVERTEXSHADER_INVALIDSHADERBYTECODE]
D3D11 ERROR: ID3D11Device::CreateVertexShader: Shader is corrupt or in an unrecognized format. [ STATE_CREATION ERROR #166: CREATEVERTEXSHADER_INVALIDSHADERBYTECODE]

CREATEVERTEXSHADER_INVALIDSHADERBYTECODEというエラーコードなので不正なバイナリということですが、原因不明のため「デバッグ情報あり」を「いいえ」にして回避しています。

「DirectX11シェーダー入門」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.