Microsoft Visual C++ 2010 Express
 Microsoft DirectX SDK (June 2010)
 Direct3D 11.0
 Shader Model 5.0
 FBX SDK 2011.3

■Direct3D 11.0 動的シェーダーリンク
( Dynamic Shader Link )
Prev Top Next
関連ページ:Direct3D 11.0 初期化 Direct3D 11.0 デバック用フォント描画 FBX SDKを使う
2012/03/11:
  1. マルチレンダーターゲットに対応するためにブレンドステートを設定する関数を修正


今回は前々からやろうと思ってた、動的シェーダーリンクをやります。
条件分岐を駆使した汎用シェーダー、いわゆるウーバー シェーダーによって作成したシェーダーは、パフォーマンスが著しく低下します。 これに対処するために条件分岐を使用せず、最小限のコーディングで実装されたシェーダーを複数用意した場合、今度はシェーダーの管理が困難になります。

このジレンマに対処すべく登場したのが動的シェーダーリンクです。 詳細は動的リンクを参照してください。

ぶっちゃけパフォーマンスについては調査してないので自分で検証してください。
ただシェーダーインターフェースを種類ごとに一つだけ用意するだけで済むので管理面では確かに楽になったといえると思います。 実際 Direct3D 9 のサンプル作成時には似たような機能のシェーダーおよびクラスを大量生産して面倒な思いをしましたしね。

では前置きはこれくらいにしてソースを見ていきます。

 


---BaseShading.hlsl---


// ************************************************************
// 入出力パラメータの定義
// ************************************************************

// 頂点シェーダーの入力パラメータ
struct VS_IN
{
   float3 pos    : POSITION;   // 頂点座標
   float3 normal : NORMAL;     // 法線
   float2 texel  : TEXCOORD;   // テクセル
};

// 頂点シェーダーの出力パラメータ
struct VS_OUT
{
   float4 sv_pos : SV_POSITION;
   float3 normal : NORMAL;
   float2 texel  : TEXCOORD0;
   float3 pos    : TEXCOORD1;
};

// ************************************************************
// インターフェースの定義
// ************************************************************

interface iBaseLight
{
   float4 GetColor( VS_OUT In );
};

// インターフェースの宣言
iBaseLight g_abstractLight;

// ************************************************************
// テクスチャーの宣言
// ************************************************************

Texture2D g_DecalMap : register( t0 );  // デカールマップ
Texture2D g_TexMap01 : register( t1 );  // 汎用目的で使用するテクスチャー

// サンプラーステート
SamplerState  g_Sampler : register( s0 );

// ************************************************************
// 共通関数
// ************************************************************

// Diffuse
float GetDiffuseColor( float3 lightdir, float3 normal )
{
   float Out;
   
   // ハーフランバートにて拡散反射率を計算する
   Out = dot( lightdir, normal );
   Out = Out * 0.5f + 0.5f;
   Out *= Out;

   return Out;
};

// Specular
float GetSpecularColor( float3 lightdir, float3 normal, float3 lookat )
{
   float Out = 0;

   // ハーフベクトルを計算する
   float3 half = normalize( lightdir + lookat );

   // 鏡面反射率を計算する
   Out = pow( max( 0.0f, dot( normal, half ) ), 50.0f );

   return Out;
};

// Material
float3 GetMaterialColor( float2 texel )
{
   float3 Out;
   
   Out = g_DecalMap.Sample( g_Sampler, texel ).rgb;
   
   return Out;
}

// ************************************************************
// クラスの実装
// ************************************************************

// ハーフランバート
class cHalfLambert : iBaseLight
{
   // 定数バッファ
   
   float4 g_vecLight;    // 平行光源の方向ベクトル

   // 色を計算する
   float4 GetColor( VS_OUT In )
   {
      float4 Out;

      float3 lightdir = normalize( -g_vecLight.xyz );
      float3 normal = normalize( In.normal );
      // 拡散反射率を計算する
      float diffuse = GetDiffuseColor( lightdir, normal );

      // 合成
      Out = float4( GetMaterialColor( In.texel ) * diffuse, 1 );

      return Out;
   }
};

// フォンシェーディング
class cPhongShading : iBaseLight
{
   // 定数バッファ

   float4 g_vecLight;    // 平行光源の方向ベクトル
   float4 g_EyePos;      // 視点座標

   // 色を計算する
   float4 GetColor( VS_OUT In )
   {
      float4 Out;

      float3 lightdir = normalize( -g_vecLight.xyz );
      float3 normal = normalize( In.normal );
      // 拡散反射率を計算する
      float diffuse = GetDiffuseColor( lightdir, normal );

      // 視線ベクトルを計算
      float3 lookat = normalize( g_EyePos.xyz - In.pos );
      // 鏡面反射率を計算する
      float specular = GetSpecularColor( lightdir, normal, lookat );

      // 合成
      Out = float4( GetMaterialColor( In.texel ) * diffuse + diffuse * specular, 1 );

      return Out;
   }
};

// セルシェーディング
class cCelShading : iBaseLight
{
   // 定数バッファ

   float4 g_vecLight;    // 平行光源の方向ベクトル

   // 色を計算する
   float4 GetColor( VS_OUT In )
   {
      float4 Out;

      float3 lightdir = normalize( -g_vecLight.xyz );
      float3 normal = normalize( In.normal );
      // 拡散反射率を計算する
      float diffuse = GetDiffuseColor( lightdir, normal );
      
      float celcolor = g_TexMap01.Sample( g_Sampler, diffuse ).r;

      // 合成
      Out = float4( GetMaterialColor( In.texel ) * celcolor, 1 );

      return Out;
   }
};

// ************************************************************
// 定数バッファの宣言
// ************************************************************

// 使用するクラスを選択するための定数バッファ
cbuffer cbBuffer0 : register( b0 )
{
  // この並び順はBASE_SHADINGクラス内の定数バッファの並び順と同じくなるようにすること
  cHalfLambert    g_HalfLambert;
  cPhongShading   g_PhongShading;
  cCelShading     g_CelShading;
};

// 頂点シェーダーで使用する定数バッファ
cbuffer cbBuffer1 : register( b1 )
{
   // 列優先
   column_major float4x4 g_matWVP : packoffset( c0 );   // ワールド × ビュー × 射影 行列
};

// ************************************************************
// シェーダーの実装
// ************************************************************

// 頂点シェーダー
VS_OUT BaseShading_VS_Main( VS_IN In )
{
   VS_OUT Out;
   Out.sv_pos = mul( float4( In.pos, 1.0f ), g_matWVP );
   Out.normal = In.normal;
   Out.texel = In.texel;
   Out.pos = In.pos;

   return Out;
}

// ピクセルシェーダ
float4 BaseShading_PS_Main( VS_OUT In ) : SV_TARGET
{
   float4 Out;

   Out = g_abstractLight.GetColor( In );

   return Out;
}

さて、interface といった見慣れないキーワードがでてきました。 このinterface というキーワードは C++ には 存在しませんが java や C# には存在します。 インターフェースとはいわゆる抽象クラスのことです。 抽象クラスは、メンバ関数の定義のみを宣言し、実装は行いません。メンバ関数の実装は、抽象クラスを継承したクラス内で行うことになります。

動的シェーダーリンクでも似たような感じに機能します。 インターフェースを継承したクラスのメンバ関数の中で実際にどのメンバ関数を使用して処理を行うかは、コンパイル時には決定されません。 メンバ関数は実行時に選択することになります。

ではサンプルソースの解説を行います。

サンプルではインターフェースを一つ用意しました。インターフェース内では GetColor() というメンバ関数の定義のみ行います。 そして GetColor() はピクセルシェーダー内で使用します。 ピクセルシェーダー側では GetColor() の処理内容については把握せず、戻り値の色情報を返すのみの実装となります。

GetColor() の実装はインターフェースを継承したクラス内で行います。 今回は、ハーフランバート、フォンシェーディング、セルシェーディングといったおなじみのシェーダーを実装しました。 ちなみに Direct3D 11 のサンプルの DynamicShaderLinkage11 ではもっと細分化してます。 汎用性を考慮するとそうしたほうがいいのでしょうが、見づらくなるのでサンプルのようにしました。
なお、各クラス内で宣言している float4 g_vecLight; などの変数は外部から定数バッファとして値を設定することができます。

最後に外部からメンバ関数を選択するための定数バッファ cbBuffer0 を用意します。 外部からは各クラスのインスタンス名を指定することで使用するメンバ関数が選択されます。 クラスを宣言する順番についてはのちほど説明します。

---BaseShading.h---


#ifndef BASE_SHADING_H
#define BASE_SHADING_H

class BASE_SHADING
{
private:
   // 動的シェーダーリンケージ( サンプルではピクセルシェーダーのみ )用の定数バッファ定義
   // この並び順はシェーダーソース内で宣言している定数バッファの並び順と同じくなるようにすること
   typedef struct _CBUFFER0
   {
      // ハーフランバート用
      D3DXVECTOR4 vecLight0;

      // フォンシェーディング用
      D3DXVECTOR4 vecLight1;
      D3DXVECTOR4 EyePos;

      // セルシェーディング用
      D3DXVECTOR4 vecLight2;
   }CBUFFER0;

   // 頂点シェーダー用の定数バッファ定義
   typedef struct _CBUFFER1
   {
      // ワールド × ビュー × 射影 行列
      D3DXMATRIX  matWVP;
   }CBUFFER1;

   // 入力レイアウト
   ID3D11InputLayout* m_pLayout;

   // 定数バッファ
   ID3D11Buffer* m_pConstantBuffers[2];

   // 頂点シェーダー
   ID3D11VertexShader* m_pVertexShader;

   // ピクセルシェーダー
   ID3D11PixelShader* m_pPixelShader;

   // 動的シェーダーリンク
   // ID3D11ClassLinkage
   ID3D11ClassLinkage* m_pPSClassLinkage;

   // ピクセルシェーダーに設定時に使用するクラスインスタンス
   // ID3D11ClassInstance
   ID3D11ClassInstance* m_pClassInstance;

public:

   BASE_SHADING();
   ~BASE_SHADING();

   // 初期化
   HRESULT Init( ID3D11Device* pD3DDevice
                 ,const BYTE* pVS, size_t VSSize
                 ,const BYTE* pPS, size_t PSSize
                 );
   HRESULT Init( ID3D11Device* pD3DDevice, TCHAR pSrcFile[], LPCSTR pVSMain, LPCSTR pPSMain );

   // 頂点シェーダー用の定数バッファを設定する
   HRESULT SetCBVertexShader( ID3D11DeviceContext* pD3DDeviceContext
                              ,D3DXMATRIX* p_matWVP
                            );
   // ハーフランバート用の定数バッファを設定
   HRESULT SetCBHalfLambert( ID3D11DeviceContext* pD3DDeviceContext
                             ,D3DXVECTOR4* p_vecLight
                             ,ID3D11ShaderResourceView* pDecalMap
                           );
   // フォンシェーディング用の定数バッファを設定
   HRESULT SetCBPhongShading( ID3D11DeviceContext* pD3DDeviceContext
                              ,D3DXVECTOR4* p_vecLight
                              ,D3DXVECTOR4* p_EyePos
                              ,ID3D11ShaderResourceView* pDecalMap
                            );
   // セルシェーディング用の定数バッファを設定
   HRESULT SetCBCelShading( ID3D11DeviceContext* pD3DDeviceContext
                            ,D3DXVECTOR4* p_vecLight
                            ,ID3D11ShaderResourceView* pDecalMap
                            ,ID3D11ShaderResourceView* pCelMap
                          );
   // 描画開始処理
   HRESULT Begin( ID3D11DeviceContext* pD3DDeviceContext, LPCSTR pClassInstanceName );
   // 描画終了処理
   void End();
};

#endif

BaseShading.hlsl の管理クラスです。注意点は CBUFFER0 定数バッファについてで、内容はコメントに書いた通りです。 なお vecLight0 〜 vecLight2 は平行光源の方向ベクトルで使用目的が同じです。 ぶっちゃけ無駄なので1つにまとめたほうがいいでしょう。 修正したい人は、 Direct3D 11 のサンプルの DynamicShaderLinkage11 を参照してください。

---BaseShading.cpp---



#include "DX11User.h"
#include "BaseShading.h"

BASE_SHADING::BASE_SHADING()
{
   m_pLayout = NULL;
   for( int i=0; i<_countof( m_pConstantBuffers ); i++ )
      m_pConstantBuffers[i] = NULL;
   m_pVertexShader = NULL;
   m_pPSClassLinkage = NULL;
   m_pPixelShader = NULL;
}

BASE_SHADING::~BASE_SHADING()
{
   SAFE_RELEASE( m_pLayout );
   for( int i=0; i<_countof( m_pConstantBuffers ); i++ )
      SAFE_RELEASE( m_pConstantBuffers[i] );
   SAFE_RELEASE( m_pVertexShader );
   SAFE_RELEASE( m_pPSClassLinkage );
   SAFE_RELEASE( m_pPixelShader );
}

// 初期化
HRESULT BASE_SHADING::Init( ID3D11Device* pD3DDevice
                            ,const BYTE* pVS, size_t VSSize
                            ,const BYTE* pPS, size_t PSSize
                           )
{
   HRESULT hr = E_FAIL;

   if( pD3DDevice == NULL ) goto EXIT;

   // *****************************************************************************************************************
   // 頂点シェーダーの作成
   // *****************************************************************************************************************

   hr = pD3DDevice->CreateVertexShader( pVS, VSSize, NULL, &m_pVertexShader );
   if( FAILED( hr ) ) goto EXIT;

   // 入力レイアウトは固定設定にしておく
   D3D11_INPUT_ELEMENT_DESC layout[] = {
         { "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 },
         { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
   };
   hr = pD3DDevice->CreateInputLayout( layout, _countof( layout ), pVS, VSSize, &m_pLayout );
   if( FAILED( hr ) ) goto EXIT;

   // *****************************************************************************************************************
   // ピクセルェーダーの作成
   // *****************************************************************************************************************

   // ピクセルシェーダー用の動的シェーダー リンクを有効にするクラス リンク ライブラリを作成する
   // http://msdn.microsoft.com/ja-jp/library/ee419783(v=vs.85).aspx
   pD3DDevice->CreateClassLinkage( &m_pPSClassLinkage );

   hr = pD3DDevice->CreatePixelShader( pPS, PSSize, m_pPSClassLinkage, &m_pPixelShader );
   if( FAILED( hr ) ) goto EXIT;

   // *****************************************************************************************************************
   // 定数バッファを作成
   // *****************************************************************************************************************

   D3D11_BUFFER_DESC BufferDesc;

   // 動的シェーダーリンケージ内で使用する定数バッファオブジェクトを作成する
   ::ZeroMemory( &BufferDesc, sizeof( BufferDesc ) );
   BufferDesc.ByteWidth             = sizeof( CBUFFER0 );        // バッファサイズ
   BufferDesc.Usage                 = D3D11_USAGE_DYNAMIC;       // リソース使用法を特定する
   BufferDesc.BindFlags             = D3D11_BIND_CONSTANT_BUFFER;// バッファの種類
   BufferDesc.CPUAccessFlags        = D3D11_CPU_ACCESS_WRITE;    // CPU アクセス
   BufferDesc.MiscFlags             = 0;                         // その他のフラグも設定しない
   hr = pD3DDevice->CreateBuffer( &BufferDesc, NULL, &m_pConstantBuffers[0] );
   if( FAILED( hr ) ) goto EXIT;

   // 頂点シェーダーで使用する定数バッファオブジェクトを作成する
   ::ZeroMemory( &BufferDesc, sizeof( BufferDesc ) );
   BufferDesc.ByteWidth             = sizeof( CBUFFER1 );        // バッファサイズ
   BufferDesc.Usage                 = D3D11_USAGE_DYNAMIC;       // リソース使用法を特定する
   BufferDesc.BindFlags             = D3D11_BIND_CONSTANT_BUFFER;// バッファの種類
   BufferDesc.CPUAccessFlags        = D3D11_CPU_ACCESS_WRITE;    // CPU アクセス
   BufferDesc.MiscFlags             = 0;                         // その他のフラグも設定しない
   hr = pD3DDevice->CreateBuffer( &BufferDesc, NULL, &m_pConstantBuffers[1] );
   if( FAILED( hr ) ) goto EXIT;

   hr = S_OK;

EXIT:
   return hr;
}

HRESULT BASE_SHADING::Init( ID3D11Device* pD3DDevice, TCHAR pSrcFile[], LPCSTR pVSMain, LPCSTR pPSMain )
{
   HRESULT hr = E_FAIL;

   ID3D10Blob* pVSBlob = NULL, * pPSBlob = NULL;

   // 行列を列優先で設定し、古い形式の記述を許可しないようにする
   UINT Flag1 = D3D10_SHADER_PACK_MATRIX_COLUMN_MAJOR | D3D10_SHADER_ENABLE_STRICTNESS;
   // 最適化レベルを設定する
#if defined(DEBUG) || defined(_DEBUG)
   Flag1 |= D3D10_SHADER_OPTIMIZATION_LEVEL0;
#else
   Flag1 |= D3D10_SHADER_OPTIMIZATION_LEVEL3;
#endif

   // 頂点シェーダーをコンパイルする
   hr = D3DX11CompileFromFile( pSrcFile, NULL, NULL, pVSMain, "vs_5_0", Flag1, 0, NULL, &pVSBlob, NULL, NULL );
   if( FAILED( hr ) ) goto EXIT;

   // ピクセルシェーダーをコンパイルする
   hr = D3DX11CompileFromFile( pSrcFile, NULL, NULL, pPSMain, "ps_5_0", Flag1, 0, NULL, &pPSBlob, NULL, NULL );
   if( FAILED( hr ) ) goto EXIT;

   // 初期化する
   hr = Init( pD3DDevice
              ,reinterpret_cast<BYTE*>(pVSBlob->GetBufferPointer()), pVSBlob->GetBufferSize()
              ,reinterpret_cast<BYTE*>(pPSBlob->GetBufferPointer()), pPSBlob->GetBufferSize()
            );
   if( FAILED( hr ) ) goto EXIT;  

   hr = S_OK;
EXIT:
   SAFE_RELEASE( pVSBlob );
   SAFE_RELEASE( pPSBlob );

   return hr;
}

// 頂点シェーダー用の定数バッファを設定する
HRESULT BASE_SHADING::SetCBVertexShader( ID3D11DeviceContext* pD3DDeviceContext, D3DXMATRIX* p_matWVP )
{
   HRESULT hr = E_FAIL;

   if( pD3DDeviceContext == NULL ) goto EXIT;
   if( p_matWVP == NULL ) goto EXIT;

   D3D11_MAPPED_SUBRESOURCE mappedResource;
   CBUFFER1* cbuffer;

   hr = pD3DDeviceContext->Map( m_pConstantBuffers[1], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
   if( FAILED( hr ) ) goto EXIT;
   cbuffer = (CBUFFER1*)(mappedResource.pData);

   ::CopyMemory( &cbuffer->matWVP, p_matWVP, sizeof( D3DXMATRIX ));

   pD3DDeviceContext->Unmap( m_pConstantBuffers[1], 0 );

   hr = S_OK;
EXIT:
   return hr;
}

// ハーフランバート用の定数バッファを設定
HRESULT BASE_SHADING::SetCBHalfLambert( ID3D11DeviceContext* pD3DDeviceContext
                                        ,D3DXVECTOR4* p_vecLight
                                        ,ID3D11ShaderResourceView* pDecalMap
                                        )
{
   HRESULT hr = E_FAIL;

   if( pD3DDeviceContext == NULL ) goto EXIT;
   if( p_vecLight == NULL ) goto EXIT;

   D3D11_MAPPED_SUBRESOURCE mappedResource;
   CBUFFER0* cbuffer;

   hr = pD3DDeviceContext->Map( m_pConstantBuffers[0], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
   if( FAILED( hr ) ) goto EXIT;
   cbuffer = (CBUFFER0*)(mappedResource.pData);

   ::CopyMemory( &cbuffer->vecLight0, p_vecLight, sizeof( D3DXVECTOR4 ));

   pD3DDeviceContext->Unmap( m_pConstantBuffers[0], 0 );

   // シェーダーリソースビューを設定
   pD3DDeviceContext->PSSetShaderResources( 0, 1, &pDecalMap );

   hr = S_OK;
EXIT:
   return hr;
}

// フォンシェーディング用の定数バッファを設定
HRESULT BASE_SHADING::SetCBPhongShading( ID3D11DeviceContext* pD3DDeviceContext
                                         ,D3DXVECTOR4* p_vecLight
                                         ,D3DXVECTOR4* p_EyePos
                                         ,ID3D11ShaderResourceView* pDecalMap
                                        )
{
   HRESULT hr = E_FAIL;

   if( pD3DDeviceContext == NULL ) goto EXIT;
   if( p_vecLight == NULL ) goto EXIT;
   if( p_EyePos == NULL ) goto EXIT;

   D3D11_MAPPED_SUBRESOURCE mappedResource;
   CBUFFER0* cbuffer;

   hr = pD3DDeviceContext->Map( m_pConstantBuffers[0], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
   if( FAILED( hr ) ) goto EXIT;
   cbuffer = (CBUFFER0*)(mappedResource.pData);

   ::CopyMemory( &cbuffer->vecLight1, p_vecLight, sizeof( D3DXVECTOR4 ));
   ::CopyMemory( &cbuffer->EyePos, p_EyePos, sizeof( D3DXVECTOR4 ));

   pD3DDeviceContext->Unmap( m_pConstantBuffers[0], 0 );

   // シェーダーリソースビューを設定
   pD3DDeviceContext->PSSetShaderResources( 0, 1, &pDecalMap );

   hr = S_OK;
EXIT:
   return hr;
}

// セルシェーディング用の定数バッファを設定
HRESULT BASE_SHADING::SetCBCelShading( ID3D11DeviceContext* pD3DDeviceContext
                                       ,D3DXVECTOR4* p_vecLight
                                       ,ID3D11ShaderResourceView* pDecalMap
                                       ,ID3D11ShaderResourceView* pCelMap
                                     )
{
   HRESULT hr = E_FAIL;

   if( pD3DDeviceContext == NULL ) goto EXIT;
   if( p_vecLight == NULL ) goto EXIT;
   if( pCelMap == NULL ) goto EXIT;

   D3D11_MAPPED_SUBRESOURCE mappedResource;
   CBUFFER0* cbuffer;

   hr = pD3DDeviceContext->Map( m_pConstantBuffers[0], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
   if( FAILED( hr ) ) goto EXIT;
   cbuffer = (CBUFFER0*)(mappedResource.pData);

   ::CopyMemory( &cbuffer->vecLight2, p_vecLight, sizeof( D3DXVECTOR4 ));

   pD3DDeviceContext->Unmap( m_pConstantBuffers[0], 0 );

   // シェーダーリソースビューを設定
   pD3DDeviceContext->PSSetShaderResources( 0, 1, &pDecalMap );
   pD3DDeviceContext->PSSetShaderResources( 1, 1, &pCelMap );

   hr = S_OK;
EXIT:
   return hr;
}

HRESULT BASE_SHADING::Begin( ID3D11DeviceContext* pD3DDeviceContext, LPCSTR pClassInstanceName )
{
   HRESULT hr = E_FAIL;

   // 指定された HLSL クラスを表すクラスインスタンス オブジェクトを取得する
   // http://msdn.microsoft.com/ja-jp/library/ee419541(v=vs.85).aspx
   hr = m_pPSClassLinkage->GetClassInstance( pClassInstanceName, 0, &m_pClassInstance );
   if( FAILED( hr ) ) goto EXIT;

   // レイアウト設定
   pD3DDeviceContext->IASetInputLayout( m_pLayout );

   // 頂点シェーダーをデバイスに設定する。
   pD3DDeviceContext->VSSetShader( m_pVertexShader, NULL, 0 );

   // 頂点シェーダーに定数バッファを設定する
   pD3DDeviceContext->VSSetConstantBuffers( 1, 1, &m_pConstantBuffers[1] );

   // ハルシェーダーを無効にする。
   pD3DDeviceContext->HSSetShader( NULL, NULL, 0 );

   // ドメインシェーダーを無効にする。
   pD3DDeviceContext->DSSetShader( NULL, NULL, 0 );

   // ジオメトリシェーダーを無効にする。
   pD3DDeviceContext->GSSetShader( NULL, NULL, 0 );
   
   // ピクセルシェーダーを動的シェーダーリンクとして設定する
   pD3DDeviceContext->PSSetShader( m_pPixelShader, &m_pClassInstance, 1 );

   // ピクセルシェーダーに定数バッファを設定する
   pD3DDeviceContext->PSSetConstantBuffers( 0, 1, &m_pConstantBuffers[0] );

   // コンピュートシェーダーを無効にする。
   pD3DDeviceContext->CSSetShader( NULL, NULL, 0 );

   hr = S_OK;
EXIT:
   return hr;
}

void BASE_SHADING::End()
{
   SAFE_RELEASE( m_pClassInstance );
}

1点だけ注意があります。筆者は超はまったんですが、 ID3D11ClassLinkage::GetClassInstance という関数について。これは、指定された HLSL クラスを表すクラスインスタンス オブジェクトを取得するための関数ですが、 この関数はクラス内にデータ メンバーが存在する場合に使用でき、存在しない場合は、 ID3D11ClassLinkage::CreateClassInstanceを使用するようにとドキュメントに明記してあります。 この記述を完全に見落としていて、コンパイルは通るけど実行時エラーになって、原因がわからずはまりました(笑)。

備忘録のためエラーの内容をのせときます。

D3D11: ERROR: ID3D11DeviceContext::PSSetShader: ppClassInstances[0]=g_HalfLambert[0] refers to an instance not available in the shader. [ STATE_SETTING ERROR #2097307: DEVICE_SETSHADER_INVALID_INSTANCE ]

---main.cpp---


#include "../../USER/DX11User.h"
#include "../../USER/D3D11User.h"
#include "../../USER/DebugFontUser.h"
#include "../../USER/BaseShading.h"

// FBX SDK 用
#include "../../USER/MeshUser.h"
#include "../../USER/FBXSDKMeshLoaderUser.h"

// シェーダーオブジェクトを作成するとき、ファイルから読むか、メモリから読むかを切り替える
#if defined(DEBUG) || defined(_DEBUG)
#define UNCOMPILED_SHADER     // ファイルを読み込んでコンパイルする

#else
// コンパイル済み頂点シェーダー
#include "../../USER/HLSL/BaseShading_BaseShading_VS_Main.h"
// コンパイル済みピクセルシェーダー
#include "../../USER/HLSL/BaseShading_BaseShading_PS_Main.h"
#endif

// アプリケーション名
TCHAR* AppName = _T("DX11_Tutrial17 Dynamic Shader Link");

// Direct3D関連の自作クラス
D3D11USER* g_pD3D11User = NULL;

// デバッグ専用のテキスト描画する自作クラス
CDebugFont* g_pDebugFontUser = NULL;

// FBX メッシュローダー
FBXSDK_MESHLOADER_USER* g_pMeshLoader = NULL;
// メッシュオブジェクト
BASE_MESH_USER*         g_pMesh = NULL;

// セルシェーディングで使用するセルマップ
ID3D11ShaderResourceView* g_pCelMap = NULL;

// サンプラーステート
ID3D11SamplerState* g_pSamplerState = NULL;

// 深度ステンシルステート
ID3D11DepthStencilState*  g_pDepthStencilState = NULL;

// シェーディング系シェーダーの基本クラス
BASE_SHADING* g_pBaseShading = NULL;

// 平行光源の方向ベクトル
D3DXVECTOR4 g_vecLight = D3DXVECTOR4( -0.5f, -0.5f, 0.5f, 0.0f );

// ビュー行列
D3DXMATRIX g_matView;

// シェーディングモード
int g_Mode = 0;

// 節電モードの制御に使用する変数。
bool Activate = true;    // ウィンドウがアクティブか
bool StandBy = false;    // スタンバイ状態か

bool ScreenShot = false; // スクリーンショットを作成するかフラグ

// リソースの初期化
HRESULT Init()
{
   HRESULT hr = E_FAIL;

   D3DX11_IMAGE_LOAD_INFO info;

   // 頂点情報を取得する
   hr = g_pMeshLoader->LoadMeshData( _T("Res/object.fbx"), &g_pMesh );
   if( FAILED( hr ) ) goto EXIT;

   // ファイルからシェーダーリソースビューを作成する
   ::ZeroMemory( &info, sizeof( D3DX11_IMAGE_LOAD_INFO ) );
   info.Width = D3DX11_DEFAULT; 
   info.Height = D3DX11_DEFAULT; 
   info.Depth = D3DX11_DEFAULT; 
   info.FirstMipLevel = D3DX11_DEFAULT;          // テクスチャーの最高解像度のミップマップ レベル。
   info.MipLevels = 0;                           // ミップマップ数。0 または D3DX11_DEFAULT を使用するとすべてのミップマップ チェーンを作成する。
   info.Usage = D3D11_USAGE_DEFAULT; 
   info.BindFlags = D3D11_BIND_SHADER_RESOURCE;
   info.CpuAccessFlags = 0;
   info.MiscFlags = 0;
   info.Format = DXGI_FORMAT_FROM_FILE;
   info.Filter = D3DX11_FILTER_POINT;            // テクスチャー読み込み時に使用するフィルター
   info.MipFilter = D3DX11_FILTER_POINT;         // ミップマップ作成時に使用するフィルター
   info.pSrcInfo = NULL;
   hr = D3DX11CreateShaderResourceViewFromFile( g_pD3D11User->m_D3DDevice, _T("res\\CelMap.png"), &info, NULL, &g_pCelMap, NULL );
   if( FAILED( hr ) ) goto EXIT;

   g_pBaseShading = NEW BASE_SHADING();
#ifndef UNCOMPILED_SHADER
   hr = g_pBaseShading->Init( g_pD3D11User->m_D3DDevice
                              ,g_BaseShading_VS_Main, sizeof( g_BaseShading_VS_Main )
                              ,g_BaseShading_PS_Main, sizeof( g_BaseShading_PS_Main )
                              );
   if( FAILED( hr ) ) goto EXIT;
#else
   hr = g_pBaseShading->Init( g_pD3D11User->m_D3DDevice
                              ,_T("../../USER/HLSL/BaseShading.hlsl")
                              ,"BaseShading_VS_Main"
                              ,"BaseShading_PS_Main"
                              );
   if( FAILED( hr ) ) goto EXIT;
#endif

   // サンプラーステートの設定
   D3D11_SAMPLER_DESC samplerDesc;
   samplerDesc.Filter = D3D11_FILTER_ANISOTROPIC;         // サンプリング時に使用するフィルタ。ここでは異方性フィルターを使用する。
   samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;    // セルシェーディングたおかしく描画されるので CLAMP
   samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;    // セルシェーディングたおかしく描画されるので CLAMP
   samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;    // セルシェーディングたおかしく描画されるので CLAMP
   samplerDesc.MipLODBias = 0;                            // 計算されたミップマップ レベルからのバイアス
   samplerDesc.MaxAnisotropy = 16;                        // サンプリングに異方性補間を使用している場合の限界値。有効な値は 1 〜 16 。
   samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;  // 比較オプション。
   ::CopyMemory( samplerDesc.BorderColor, &D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 0.0f ), sizeof( D3DXVECTOR4 ) ); // 境界色
   samplerDesc.MinLOD = 0;                                // アクセス可能なミップマップの下限値
   samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;                // アクセス可能なミップマップの上限値
   hr = g_pD3D11User->m_D3DDevice->CreateSamplerState( &samplerDesc, &g_pSamplerState );
   if( FAILED( hr ) ) goto EXIT;

   // 深度ステンシルステートを作成する
   D3D11_DEPTH_STENCIL_DESC ddsDesc;
   ::ZeroMemory( &ddsDesc, sizeof( ddsDesc ) );
   ddsDesc.DepthEnable = TRUE;                                     // 深度テストを使用する
   ddsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
   ddsDesc.DepthFunc = D3D11_COMPARISON_LESS;
   ddsDesc.StencilEnable = FALSE;
   hr = g_pD3D11User->m_D3DDevice->CreateDepthStencilState( &ddsDesc, &g_pDepthStencilState );
   if( FAILED( hr ) ) goto EXIT;

   // ビュー行列
   D3DXMatrixLookAtLH( &g_matView,
                       &D3DXVECTOR3( 0.0f, 0.0f, -10.0f ),
                       &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),
                       &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );

   hr = S_OK;

EXIT:
   return hr;
}

// メモリ開放
void Invalidate()
{
   SAFE_DELETE( g_pBaseShading );
   SAFE_DELETE( g_pMesh );
   SAFE_DELETE( g_pMeshLoader );
   SAFE_RELEASE( g_pDepthStencilState );
   SAFE_RELEASE( g_pSamplerState );
   SAFE_RELEASE( g_pCelMap );
   SAFE_DELETE( g_pDebugFontUser );
   SAFE_DELETE( g_pD3D11User );
}

// 描画処理
HRESULT Render()
{
   HRESULT hr = E_FAIL;
   D3DXMATRIX matScaling, matTranslation, matWorld, matInv, matProj, matWVP, matWV;
   D3DXVECTOR4 vec4LightDir, vec4EyePos;

   float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

   // バックバッファをクリア
   g_pD3D11User->m_D3DDeviceContext->ClearRenderTargetView( g_pD3D11User->m_RenderTargetView, ClearColor ); 

   // 深度バッファをクリア
   if( g_pD3D11User->m_DepthStencilView )
      g_pD3D11User->m_D3DDeviceContext->ClearDepthStencilView( g_pD3D11User->m_DepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 );

   // 深度ステンシルステートをセット.
   g_pD3D11User->m_D3DDeviceContext->OMSetDepthStencilState( g_pDepthStencilState, 0 );

   // アルファブレンディングを無効
   D3D11_RENDER_TARGET_BLEND_DESC BlendDesc;
   BlendDesc = g_pD3D11User->GetDefaultBlendDesc();
   g_pD3D11User->SetBlendState( &BlendDesc, 1, FALSE );

   // 頂点バッファ設定
   UINT stride = sizeof( MESH_USER::VERTEX_USER );
   UINT offset = 0;
   g_pD3D11User->m_D3DDeviceContext->IASetVertexBuffers( 0, 1, &g_pMesh->MeshUser[0].VertexBuffer, &stride, &offset );

   // インデックスバッファ設定
   g_pD3D11User->m_D3DDeviceContext->IASetIndexBuffer( g_pMesh->MeshUser[0].IndexBuffer, DXGI_FORMAT_R32_UINT, 0 );

   // 三角ポリゴン描画
   g_pD3D11User->m_D3DDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

   // 定数バッファにセット
   {
      // 射影行列
      D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI / 2.0f, 4.0f / 3.0f, 1.0f, 50.0f );

      // ワールド座標系の行列

      // スケーリング行列
      D3DXMatrixScaling( &matScaling, 3.0f, 3.0f, 3.0f );
      // 平行移動行列
      D3DXMatrixTranslation( &matTranslation, 0.0f, 0.0f, 0.0f );
      // ワールド座標系の行列の合成
      matWorld = matScaling * matTranslation;

      // 行列を合成
      matWVP = matWorld * g_matView * matProj;
      // シェーダー内では列優先にしているので転置行列を作成する。
      D3DXMatrixTranspose( &matWVP, &matWVP );

      // 頂点シェーダー用の定数バッファを設定する
      g_pBaseShading->SetCBVertexShader( g_pD3D11User->m_D3DDeviceContext, &matWVP );

      // 平行光源の方向ベクトル

      // ワールド行列の逆行列を作成
      D3DXMatrixInverse( &matInv, NULL, &matWorld );
      D3DXVec4Transform( &vec4LightDir, &g_vecLight, &matInv );

      // 視点座標

      // ビュー座標系の逆行列を作成
      matWV = matWorld * g_matView;
      D3DXMatrixInverse( &matInv, NULL, &matWV );
      D3DXVec4Transform( &vec4EyePos, &D3DXVECTOR4( 0, 0, 0, 1 ), &matInv );
   }

   // ピクセルシェーダーにサンプラーステートを設定する。
   g_pD3D11User->m_D3DDeviceContext->PSSetSamplers( 0, 1, &g_pSamplerState );

   ID3D11ShaderResourceView* pDecalMap = NULL;
   g_pMesh->MeshUser[0].GetTexture( _T("DiffuseColor"), &pDecalMap );

   switch( g_Mode )
   {
   case 0:
      // 定数シェーダーの設定
      hr = g_pBaseShading->SetCBHalfLambert( g_pD3D11User->m_D3DDeviceContext, &vec4LightDir, pDecalMap );
      if( FAILED( hr ) ) goto EXIT;
      // シェーダーの設定
      hr = g_pBaseShading->Begin( g_pD3D11User->m_D3DDeviceContext, "g_HalfLambert" );
      if( FAILED( hr ) ) goto EXIT;
      break;
   case 1:
      // 定数シェーダーの設定
      hr = g_pBaseShading->SetCBPhongShading( g_pD3D11User->m_D3DDeviceContext, &vec4LightDir, &vec4EyePos, pDecalMap );
      if( FAILED( hr ) ) goto EXIT;
      // シェーダーの設定
      hr = g_pBaseShading->Begin( g_pD3D11User->m_D3DDeviceContext, "g_PhongShading" );
      if( FAILED( hr ) ) goto EXIT;
      break;
   case 2:
      // 定数シェーダーの設定
      hr = g_pBaseShading->SetCBCelShading( g_pD3D11User->m_D3DDeviceContext, &vec4LightDir, pDecalMap, g_pCelMap );
      if( FAILED( hr ) ) goto EXIT;
      // シェーダーの設定
      hr = g_pBaseShading->Begin( g_pD3D11User->m_D3DDeviceContext, "g_CelShading" );
      if( FAILED( hr ) ) goto EXIT;
      break;
   }

   // インデックスバッファを使用した描画
   g_pD3D11User->m_D3DDeviceContext->DrawIndexed( g_pMesh->MeshUser[0].IndexesCount, 0, 0 );

   g_pBaseShading->End();

   if( g_pDebugFontUser )
   {
      // デバッグ専用フォント描画
      g_pDebugFontUser->RenderFPS( g_pD3D11User->m_D3DDeviceContext, 0, 0 );

      switch( g_Mode )
      {
      case 0:
         g_pDebugFontUser->RenderDebugText( g_pD3D11User->m_D3DDeviceContext, "Half Lambert", 0.0f, 0.1f );
         break;
      case 1:
         g_pDebugFontUser->RenderDebugText( g_pD3D11User->m_D3DDeviceContext, "Phong Shading", 0.0f, 0.1f );
         break;
      case 2:
         g_pDebugFontUser->RenderDebugText( g_pD3D11User->m_D3DDeviceContext, "Cel Shading", 0.0f, 0.1f );
         break;
      }
   }

   // レンダリングされたイメージをユーザーに表示。
   hr = g_pD3D11User->m_SwapChain->Present( 0, 0 );

   if( ScreenShot )
   {
      // スクリーンショット作成
      g_pD3D11User->CreateScreenShot();
      ScreenShot = false;
   }

   hr = S_OK;
EXIT:
   return hr;
}

// 節電処理および描画処理
HRESULT PowerSavingAndRender()
{
   HRESULT hr = E_FAIL;

   switch( StandBy )
   {
   // スタンバイモード
   case  true:
      // テストのみ行い、描画処理は行わない。
      hr = g_pD3D11User->m_SwapChain->Present( 0, DXGI_PRESENT_TEST );
      switch( hr )
      {
      // いまだスタンバイ中。。。
      case DXGI_STATUS_OCCLUDED:
         // 電源管理によるスリープ状態の場合ここにくる。
         // フルスクリーンモード時にスクリーンセーバーが起動時した場合は、表示モードが強制的にウィンドウモードに変更されるためここにこない。
         goto EXIT;
         break;
      case S_OK:
         // フルスクリーンモード時にスクリーンセーバーが起動時した場合は表示モードが強制的にウィンドウモードに変更される。
         // ウィンドウモードの場合スタンバイから復帰してしまうため、ウィンドウがアクティブになったときに復帰するようにする。
         if( Activate == true )
         {
            // たまにウィンドウが表示されないときがあるので表示するようにする
            ::ShowWindow( g_pD3D11User->m_hWnd, SW_SHOW );
            StandBy = false;
         }
         break;
      default:
         goto EXIT;
         break;
      }
      break;
   // スタンバイモードでない
   case false:
      // 描画処理
      hr = Render();
      if( FAILED( hr ) ) goto EXIT;

      switch( hr )
      {
      case DXGI_STATUS_OCCLUDED:
         // スタンバイモードへ移行
         // フルスクリーンモード時のスクリーンセーバー起動時、
         // スリープ状態に移行した時に発生する。
         StandBy = true;
         goto EXIT;
         break;
      case S_OK:
         break;
      default:
         goto EXIT;
         break;
      }
      break;
   }

   hr = S_OK;

EXIT:

   return hr;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam )
{
   switch( msg )
   {
   case WM_KEYUP:
      // アプリ終了
      switch( wParam )
      {
      case VK_ESCAPE:
         ::DestroyWindow( hWnd );
         break;

      case VK_F2:
         // F2キーを押すと、ウィンドウモードを切り替える。
         // 自動的にウィンドウモードを切り替える機能もあるが、ウィンドウスタイルを自由に変更するために自分で実装することにした。
         g_pD3D11User->ChangeWindowMode();
         break;

      case VK_SNAPSHOT:
         // スクリーンショットを作成する
         ScreenShot = true;
         break;

      case 'S':
         // シェーディングモード変更
         switch( g_Mode )
         {
         case 0:
            g_Mode = 1;
            break;
         case 1:
            g_Mode = 2;
            break;
         case 2:
            g_Mode = 0;
            break;
         }
         break;
      }
      break;

   case WM_KEYDOWN:
      switch( wParam )
      {
      case VK_UP:
         {
            D3DXMATRIX mat;
            D3DXMatrixTranslation( &mat, 0, 0, -1.0f ); 
            g_matView *= mat;
         }
         break;
      case VK_DOWN:
         {
            D3DXMATRIX mat;
            D3DXMatrixTranslation( &mat, 0, 0, 1.0f ); 
            g_matView *= mat;
         }
         break;
      case VK_RIGHT:
         {
            D3DXMATRIX mat;
            D3DXMatrixRotationY( &mat, -0.1f ); 
            g_matView *= mat;
         }
         break;
      case VK_LEFT:
         {
            D3DXMATRIX mat;
            D3DXMatrixRotationY( &mat, 0.1f ); 
            g_matView *= mat;
         }
         break;
      }
      break;

   case WM_ACTIVATE:
      Activate = true;
      break;

// フルスクリーンからウィンドウモードに切り替えるとき WM_SIZE イベントが発生せず、結果的に IDXGISwapChain::ResizeBuffers がコールされない。
// 環境にもよるようだが、画面上に何も表示されない現象が発生する可能性があるので
// D3D11USER::ChangeWindowModeOptimization() は D3D11USER::ChangeWindowMode() 内でコールするように修正し、ここでの処理は無効にする
//   case WM_SIZE:
//      g_pD3D11User->ChangeWindowModeOptimization();
//      break;

   case WM_DESTROY:
      Invalidate();
      ::PostQuitMessage(0);
      break;

   default:
      return ::DefWindowProc( hWnd, msg, wParam, lParam );
   }

   return 0L;
}

// メイン関数
int APIENTRY _tWinMain( HINSTANCE hInstance,
                        HINSTANCE /*hPrevInstance*/,
                        LPTSTR    /*lpCmpLine*/,
                        INT       /*nCmdShow*/ )
{
   HRESULT hr = E_FAIL;
   MSG msg;
   ::ZeroMemory(&msg, sizeof(MSG));

   // メモリリーク検出
   _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_EVERY_1024_DF);

   // 表示モードを記述するための構造体。
   DXGI_MODE_DESC sd;

   // Direct3D 関連自作クラスのインスタンスを作成
   g_pD3D11User = new D3D11USER();

   // ディスプレイモード一覧を取得する。
   // 取得した値はクラス内部に保持される。
   hr = g_pD3D11User->GetDisplayMode();
   if( FAILED( hr ) )
   {
      ::MessageBox( NULL, _T("ディスプレイモード取得エラー"), _T("初期化エラー"), MB_OK );
      goto EXIT;
   }
   // とりあえず最初に見つかったディスプレイモードを選択する
   CopyMemory( &sd, &g_pD3D11User->m_DisplayModeDesc[0], sizeof( DXGI_MODE_DESC ) );

   // ウィンドウの作成およびDirect3D の初期化
   hr = g_pD3D11User->InitD3D11( AppName, hInstance, WndProc, &sd, TRUE, TRUE, TRUE, TRUE );
   if( FAILED( hr ) )
   {
      ::MessageBox( NULL, _T("Direct3D 11.0 初期化エラー"), _T("初期化エラー"), MB_OK );
      goto EXIT;
   }

   // デバッグ専用フォント出力クラスの作成処理
   // デバックコンパイル時のみ使用する
#if defined(DEBUG) || defined(_DEBUG)
   g_pDebugFontUser = new CDebugFont();
   hr = g_pDebugFontUser->Create( g_pD3D11User->m_D3DDevice, 0.015f, 0.05f );
   if( FAILED( hr ) )
   {
      ::MessageBox( NULL, _T("デバックフォントクラス初期化エラー"), _T("初期化エラー"), MB_OK );
      goto EXIT;
   }
#endif

   // 自作のメッシュローダーの初期化
   g_pMeshLoader = new FBXSDK_MESHLOADER_USER();
   g_pMeshLoader->Initialize( g_pD3D11User->m_D3DDevice );

   // リソースの初期化
   hr = Init();
   if( FAILED( hr ) )
   {
      ::MessageBox( NULL, _T("リソース初期化エラー"), _T("初期化エラー"), MB_OK );
      goto EXIT;
   }
   
   ::ShowWindow(g_pD3D11User->m_hWnd, SW_SHOW);
   ::UpdateWindow(g_pD3D11User->m_hWnd);

   // メッセージループ
   do
   { 
      if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
      {
         ::TranslateMessage(&msg); 
         ::DispatchMessage(&msg); 
      }
      else
      {
         hr = PowerSavingAndRender();
         if( FAILED( hr ) )
            ::DestroyWindow( g_pD3D11User->m_hWnd );
      }
   }while( msg.message != WM_QUIT );

EXIT:
   if( g_pD3D11User && g_pD3D11User->m_hWnd )
      ::DestroyWindow( g_pD3D11User->m_hWnd );

   ::UnregisterClass( AppName, hInstance );

   return msg.wParam;
}

だいぶ短くなってよかったよかった。ようやくまともなシェーダープログラミングができそうだ。

あ、書き忘れた。動的シェーダーリンクは SM 5.0 で使用できます。


web拍手 by FC2

Prev Top Next

inserted by FC2 system