HLSL(fx)からアセンブラ(pso,vso)に持って行く方法

訳あってプログラムがHLSL使えないけど、なんとかアセンブラにして持って行きたい!
そんな貴方の夢を叶えちゃうんです。
とまではいきませんが、ツール上でHLSLでカキカキして実験しながら作業して、HLSLが扱えないプログラムに読み込ませるなんてことをしたい人向けです。


・FX Composer1.8をダウンロードする
http://developer.nvidia.com/object/fx_composer_18.html
まず最初にHLSL開発環境をダウンロードしましょう。
最新版は1.1とか2.0がどうも吐けないようなので。
最新版でも吐けるのでそっちを使う方がいいでしょう。
const定数の対応表もばっちし出力してくれますので。
http://d.hatena.ne.jp/Ko-Ta/20100711/p1


・HLSLの構造を改変
まず左上のMaterialを右クリックして「New Material」→「VertexLighting PixelShader」を選びます。
すると、球体が現れてソースコードが現れます。
典型的なPhongモデルのHLSLソースになります。


・定数と変数
紛らわしいので最初にコレを説明しておきます。
関数の外の場合、アセンブラに変換する上では以下の2つは全く同じです。

const float4 aaa ={0,0,0,0};
float4 aaa ={0,0,0,0};

どちらも定数レジスタ「c0,c1〜」に変換されます。
ただし、関数内の場合はちょっとちがって、

const float4 aaa ={0,0,0,0}; → c?レジスタを使用する
float4 aaa ={0,0,0,0}; → r?レジスタを使用する

ということになるそうです。
とりあえず、深く考えず、頭の隅に置いておきましょう。


・テクスチャの位置
テクスチャはSceneウインドウの球体選んで、Propertiesタブからテクスチャを操作することが出来ます。
ソースの位置では以下の場所になります。消した場合はコードを打ち込まないと復活しません。

texture diffuseTexture : Diffuse
<
	string ResourceName = "default_color.dds";
>;


・定数・各種ライト設定
ライトの設定ですが、これはShaderに渡す定数に相当します。

float4 lightDir : Direction
<
	string Object = "DirectionalLight";
    string Space = "World";
> = {1.0f, -1.0f, 1.0f, 0.0f};

とごちゃごちゃ書いてますが、以下と同じです。

float4 lightDir : Direction = {1.0f, -1.0f, 1.0f, 0.0f};

要するに定数や変数の宣言に変わり有りません。<>内はツールで提供するGUIに関する設定項目です。


・テクスチャ補間の設定
補間方法が表記されています。
特殊なShaderを書く場合はこの設定項目を変える必要も出てくるでしょう。

sampler TextureSampler = sampler_state 
{
    texture = <diffuseTexture>;
    AddressU  = CLAMP;        
    AddressV  = CLAMP;
    AddressW  = CLAMP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};


・どのアセンブラバージョンでコンパイルするか?
PixelShader,VertexShaderで使用するバージョンを設定します。
それぞれ「vs/ps_1_1」となっているところの数字を弄って下さい。「ps_2_0」とするとShader2.0みたいです。

technique textured
{
    pass p0 
    {		
		VertexShader = compile vs_1_1 VS_TransformAndTexture();
		PixelShader  = compile ps_1_1 PS_Textured();
    }
}

・VertexShaderの入力と出力フォーマット
VertexShaderにつっこむ時(in)、受け取る時(out)のデータ構造体の定義です。
outはそのままPixelShaderのinに相当します。
自由に設計・・・できるわけではないので、その辺各自で調べましょう。

struct vertexInput {
    float3 position				: POSITION;
    float3 normal				: NORMAL;
    float4 texCoordDiffuse		: TEXCOORD0;
};

struct vertexOutput {
    float4 hPosition		: POSITION;
    float4 texCoordDiffuse	: TEXCOORD0;
    float4 diffAmbColor		: COLOR0;
    float4 specCol			: COLOR1;
};


・VertexShaderプログラム部分
頂点シェーダのプログラム部分です。ここにHLSLでゴリゴリ書いていきます。

vertexOutput VS_TransformAndTexture(vertexInput IN) 
{
    vertexOutput OUT;
    OUT.hPosition = mul( float4(IN.position.xyz , 1.0) , worldViewProj);
    OUT.texCoordDiffuse = IN.texCoordDiffuse;
(略)
    return OUT;
}

・PixelShaderプログラム部分
ピクセルシェーダプログラム部分です。ここにXLSLでゴリゴリ書いていきます。

float4 PS_Textured( vertexOutput IN): COLOR
{
  float4 diffuseTexture = tex2D( TextureSampler, IN.texCoordDiffuse );
  return IN.diffAmbColor * diffuseTexture + IN.specCol;
}

とこれがおおまかな構造です。
まず色々と弄りながら、ツール上で設計していきます。


アセンブラを確認する
ソースが正常にコンパイルできると、右の「ShaderPerfタブ」に出力されます。
コンパイルは上のメニューから選べます。
上記の例ではVertexShaderとPixelShaderが確認できます。

****************************************
Target: GeForce 6200 (NV44) :: Unified Compiler: v77.72
 ** Error compiling shader ** 
****************************************
PS Instructions: 2
ps_1_1
tex t0
mad r0, t0, v0, v1

PS_1_1から下がShader部分です。右クリックでコピーできます。


・プログラムに組み込む!
さぁてここからがややこしくなります。
FX Composerはアセンブラにする際に最適化してくれます。
HLSLのまま扱えるとその辺を考えずに書くことが出来るのですが、アセンブリを使用する場合は「定数の扱い」に注意する必要があります。


XLSLでは定数はどこに書いても大丈夫ですが、アセンブラに変換されると名前スペースはなくなり、定数レジスタ「c0,c1...」に変換されます。
要するに迷子になっちゃうわけです。
ただ、ちゃんとした法則性があるのでそれを守れば迷子になりません。


・関数内で宣言した定数

float4 PS_Textured( vertexOutput IN): COLOR
{
  const float4 vAdd00 = {-0.0039,0, 0,0};
  〜
}

関数内で定数を宣言した場合は、かなり最適化してくれます。
かなりはどういうことかというと、float2しか使っていない場合は、他のfloat2を上位と下位に配置して、レジスタを節約したりします。
つまり、外部から手に負えないぐらい迷子になりますので、プログラムから弄る必要のないもののみ関数内で宣言して下さい。
要するに、扱いはローカルってことですね。


・関数外で宣言した定数

const float4 vAdd00 = {-0.0039,0, 0,0};

float4 PS_Textured( vertexOutput IN): COLOR
{  
  〜
}

プログラムから設定するような定数は必ず関数の外に宣言しましょう。
外に宣言することで、コンパイラの最適化(値を合体させる)がされなくなります。


・定数のナンバリングと法則性
コンパイルすると定数の名前が消えてしまいます。
実際にプログラムに組み込む際は、定数???が定数レジスタの何番目になっているのかを読み取って「SetPixel(Vertex)ShaderConstantF」に与えて上げないとイケマセン。


まずVertexShader、PixelShaderそれぞれで使用する変数をそれぞれでまとめましょう。
もし共有するものが有れば、それは分解しておきましょう。

//------------VertexShader
const float4 vUV00 = {1,0,0,0,0};
vertexOutput VS_TransformAndTexture(vertexInput IN) 
{
 〜
}

//------------PixelSHader
const float4 vAdd00 = {-0.0039,0, 0,0};
const float4 vAdd01 = {-0.0039,0, 0,0};

float4 PS_Textured( vertexOutput IN): COLOR
{  
  const float4 vDiff00 = {1,2,3,4};
}

定数は上から順番にカウントして格納されていきます。
つまり、上の例だと

・VertexShader
c0...vUV00
・PixelShader
c0...vAdd00
c1...vAdd01
c2...vDiff00

となります。
ただし、もし未使用の定数があった場合はその定数はスキップされ、番号がずれていくので注意して下さい。
ちょっとソースを削ってテスト出力させたら定数がずれてあれ?ってことがあるので注意です。
関数内の定数「vDiff00」も基本的に上から順にカウントされ「c2」に格納されますが、上でも述べたように最適化で他の定数と合体されたりするので、事実上プログラムから弄れません。


pso,vso出力、コンパイル
出来たら、「ShaderPref」タブからアセンブラをコピーして、「ps_sample.psa」「vs_sample.vsa」とファイルを付くって貼り付けましょう。
バージョン表記「PS.2.0」より上の部分は不要なのでコメントアウトか削除しましょう。
あとは、DirectXSDKのコンパイラをつかってコンパイルして完了です。

path [path]:\Microsoft DirectX SDK\Utilities\Bin\x86\

psa ps_sample.psa
vsa vs_sample.vsa

とバッチファイルでも作っておくといいでしょうか。


・プログラムでの組込
PixelShader、VertexShaderでアセンブラを読み込ませて、あとは定数の設定です。
定数の番号は上記の通りですので、法則性に従って「SetPixelShaderConstantF,SetPixelShaderConstantF」で設定します。
今日は持ち込むまでが本題なのでこのへんは割愛します。


うおーなげぇ