シェーダエフェクト 自作FXファイルフォーマット

投稿日:

前回、使用するシェーダ関数名やステートをソースコードに直接記述していたので、これらの情報を専用のファイルで定義するようにします。DirectX11はfxファイル(techniqueやpassの定義)に対応していないようなので、DirectX9のfxファイルを参考に独自のfxファイルを作成します。

左:きれいな半透明 中:アニメ調 右:汚い半透明
左:きれいな半透明 中:アニメ調 右:汚い半透明

自作FXファイルフォーマット

DirectX11でのFXファイル

DirectX11では、今まであったtechniqueやpassの定義に対応していないようです。DirectXTKにもありません。11になってからいろんな機能が削除されてしまっています。「必要なものは自分で作れ」という方針になったような気がします。
なので、techniqueやpassの対応を自作することにしました。

zgfx(勝手に命名)自作FXファイルフォーマット

フォーマットはDirectX9のfxファイルを参考に作成します。対応する定義は、シェーダ関数、techniqueとpass、pass内のステート設定。ステート設定は、D3D11_RASTERIZER_DESC、D3D11_BLEND_DESC、D3D11_DEPTH_STENCIL_DESCの各メンバの値を設定可能にする。ステート値は、enum宣言の一部(例 D3D11_CULL_FRONTのFRONT)を記述。

//shader/shader.fx
//省略
//テクスチャや定数バッファ

//----------------------------------------------
// アニメ調シェーダ パス0
// 法線方向に拡大
PS_INPUT vsAnimeShaderP0( VS_INPUT input)
{
	//省略
}
// 黒で描画
float4 psAnimeShaderP0( PS_INPUT input ) : SV_Target
{
	//省略
}

//----------------------------------------------
// アニメ調シェーダ パス1
PS_INPUT vsAnimeShaderP1( VS_INPUT input)
{
	//省略
}
//2段階の陰影
float4 psAnimeShaderP1( PS_INPUT input ) : SV_Target
{
	//省略
}

//--------------------------
// アルファブレンドシェーダ
// モデル全体のカラー、透明度を反映
PS_INPUT vsAlphaBlend( VS_INPUT input)
{
	//省略
}
float4 psAlphaBlend( PS_INPUT input ) : SV_Target
{
	//省略
}

//--------------------------
// テクスチャのみ
PS_INPUT vsBasic( VS_INPUT input)
{
	//省略
}
float4 psBasic( PS_INPUT input ) : SV_Target
{
	//省略
}

//シェーダエフェクト定義
#ifdef ZGFX

	//単純描画
	technique BasicShader
	{
		pass P0
		{
			VertexShader = compile vs_4_0 vsBasic();
			PixelShader = compile ps_4_0 psBasic();
		}	
	}

	//アニメ調シェーダエフェクト
	technique AnimeShader
	{
		//法線方向拡大、裏面のみを黒く描画
		pass P0
		{
			CullMode = FRONT;
			VertexShader = compile vs_4_0 vsAnimeShaderP0();
			PixelShader = compile ps_4_0 psAnimeShaderP0();
		}
		//通常描画
		pass P1
		{
			CullMode = BACK;
			VertexShader = compile vs_4_0 vsAnimeShaderP1();
			PixelShader = compile ps_4_0 psAnimeShaderP1();
		}
	}
	//汚い半透明シェーダエフェクト
	technique AlphaBlend
	{
		pass P0
		{
			CullMode = BACK;
			
			//アルファブレンド
			BlendEnable[0] = TRUE;
			SrcBlend[0] = SRC_ALPHA;
			DestBlend[0] = INV_SRC_ALPHA;
			BlendOp[0] = ADD;
			SrcBlendAlpha[0] = ONE;
			DestBlendAlpha[0] = ZERO;
			BlendOpAlpha[0] = ADD;
			RenderTargetWriteMask[0] = ALL;
			
			DepthEnable = TRUE;
			DepthFunc = LESS;
			
			VertexShader = compile vs_4_0 vsAlphaBlend();
			PixelShader = compile ps_4_0 psAlphaBlend();
		}
	}

	//綺麗な半透明シェーダエフェクト
	technique AlphaBlend2
	{
		//デプス値だけ更新するパス
		pass P0
		{
			CullMode = BACK;
			
			BlendEnable[0] = FALSE;
			RenderTargetWriteMask[0] = 0;//カラー値を更新しない
			
			DepthEnable = TRUE;
			DepthFunc = LESS;
			
			VertexShader = compile vs_4_0 vsAlphaBlend();
			PixelShader = compile ps_4_0 psAlphaBlend();
		}
		
		//通常描画パス
		pass P1
		{
			CullMode = BACK;
			
			BlendEnable[0] = TRUE;
			SrcBlend[0] = SRC_ALPHA;
			DestBlend[0] = INV_SRC_ALPHA;
			BlendOp[0] = ADD;
			SrcBlendAlpha[0] = ONE;
			DestBlendAlpha[0] = ZERO;
			BlendOpAlpha[0] = ADD;
			RenderTargetWriteMask[0] = ALL;
			
			//デプス値が一致するピクセルだけ描画
			//奥の余計なポリゴンを描画しない
			DepthEnable = TRUE;
			DepthFunc = EQUAL;
			
			VertexShader = compile vs_4_0 vsAlphaBlend();
			PixelShader = compile ps_4_0 psAlphaBlend();
		}
	}

#endif//ZGFX

簡単な構造なので、特に説明はなくていいと思います。
#ifdef ZGSX ~ #endifは、そのままだとシェーダコンパイル時にエラーになるので#ifdefマクロで無効化しておきます。zgfx定義の範囲判定にも使用しています。

サンプルプログラム

ダウンロード
zgfx自作FXファイルで描画

zgfxの解析 再びboost spirit qi

以前、もう使わないかもと言っていましたがパーサを書くのが面倒なのでboost spiritを使用してzgfxファイルの解析を行いました。今回手間取ったのは、コメント処理と#ifdefの解析。プリプロセッサは使えないので、空白文字を空白+改行+コメントとして処理。簡単にできそうだった「指定文字列までスキップ」も少し悩みました。

//zg_shdfx_parse.cpp
// #ifdef ZGSFXまでスキップ
qi::parse(first, last,
		*((*(char_-'#')>>'#') - ("ifdef">>+space>>"ZGFX") )
		>> "ifdef" >> +space >>"ZGFX" >> *skip );

// コメント
template<typename Iterator, typename A1=qi::unused_type>
struct comment_type : qi::grammar<Iterator, A1()>
{
   comment_type() : comment_type::base_type(r)
   {
		r = ( "//" >> *(char_ - eol) >> eol)
			|| ( "/*" >> *((*(char_-'*')>>+char_('*')) - '/') >> '/' )
			;
   }
   qi::rule<Iterator, A1()> r;
};

//スキップ文字(空白、改行、コメント)
template<typename Iterator, typename A1=qi::unused_type>
struct skip_type : qi::grammar<Iterator, A1()>
{
    skip_type() : skip_type::base_type(r)
    {
		r = +space
			|| comment
			;
    }
	qi::rule<Iterator, A1()> r;
	comment_type<Iterator,A1> comment;
};

class ShaderEffect;

zgfxファイルに対応したクラス。DirectX11リソース(シェーダ、シェーダコードバイナリ、ステート)を作成、保持します。このクラスと3Dモデルの形状データと組み合わせて描画処理を実行します(組み合わせ用のクラスがShaderPass)。

//zg_shdfx.h/cpp
class ShaderEffect;
typedef SPtr<ShaderEffect>::Type SEffectPtr;

// シェーダエフェクト
class ShaderEffect
{
public:
	struct PASS{
		//DirectX11リソース(インターフェイス)
		IVShaderPtr pVShader;	// 頂点シェーダ
		IBlobPtr pVSCode;		// 頂点シェーダバイナリコード レイアウト作成用
		IPShaderPtr pPShader;	// ピクセルシェーダ
		IBlobPtr pPSCode;		// ピクセルシェーダバイナリコード

		IRsStatePtr pRsState;	// ラスタライザステート
		IBdStatePtr pBdState;	// ブレンドステート
		IDsStatePtr pDsState;	// デプスステンシルステート
	};

	struct TECHNIQUE{
		TECHNIQUE(){}
		TECHNIQUE(const std::string& name) : strName(name){}
		std::string strName;
		std::vector<PASS> aPass;
	};

	std::vector<TECHNIQUE> aTech;
};

//--------------------------------------------
// zgfxファイルから構築
SEffectPtr CreateShaderEffectFromFile(const WCHAR* file);

ShaderPassのソート

zgfxファイルから作成したShaderEffectクラスと3Dモデルデータを組み合わせたクラスがShaderPass。ShaderPassクラスは、独立(単独で描画処理、処理順序に非依存)した処理なのでソートが可能です。サンプルプログラムでは、モデル番号とパス番号でソートすることで、きれいな半透明処理を実現しています(パス0:Z値だけを先に更新、パス1:Z値が一致するピクセルだけ描画)。ソートしない場合、マテリアル単位でパス0、パス1と交互に実行されて中途半端に透けた状態になります。

//zg_dx11.cpp
std::vector<REN_PASS> pass_list;

zgU32 mdl_cnt = 0;
for(auto& mdl : g_aModel){
  //省略
  for(auto& obj : mdx11.apObj){
    //省略
    for(auto &mesh : obj->apMesh){
      //省略
      for(zgU32 ipass=0;ipass<tech.numPass;++ipass){
        // パスリストに追加
        pass_list.push_back(REN_PASS((ipass | mdl_cnt<<8),tech.apPass[ipass]));
        // モデルとパスでソート
        // (ipass|mdl_cnt<<8)この値でソート
      }
    }
  }
  ++mdl_cnt;
}

// パスでソート
// ShaderPassが独立した処理で順番に依存しないのでソートができる
std::sort(pass_list.begin(),pass_list.end()); //

サンプルプログラムダウンロード
zgfx自作FXファイルで描画

コメントを残す

メールアドレスが公開されることはありません。