SpriteBatchで描画される対象のレジスタ番号は0
About
- &ref(): File not found: "WindowsGame1_SpriteBatchAlwaysUseRegisterIndex0.zip" at page "ComputerGraphics/HLSL/XNA/SpriteBatchAlwaysUseRegisterIndex0";
- VS2010
- XNA4.0
例えばシェーダを利用して2枚の画像を合成する場合などに、シェーダ内でTextureとそのSamplerを用意して、合成用の画像をそこに設定します。そうすると合成前の画像を描画しようとするときに、シェーダ内で合成用に設定したTextureを参照することになります。ところがXNAのSpriteBatchとPixelShaderでこの処理を実行しようとして不具合が生じる場合があります。PixelShader内で合成用に用意したTextureのSamplerを参照しようとすると、なぜかSpriteBatchによって描画しようとしている画像のSamplerに切り替わってしまう現象です。要するに設定したはずのTextureとそのSamplerが参照されない症状に陥ります。
この現象は特定条件下の時に発生するので、気が付きにくい場合もあるかもしれません。XNAのSpriteBatchでは、SpriteBatchを利用して描画しようとするオブジェクトへの参照を、レジスタ番号0のTexture/Samplerに代入(上書き)しているようです。よってレジスタ番号0に指定して用意した参照用のTextureやそのSamplerは全て破棄されてしまうのです。
検証・Verification
検証内容
2枚のテクスチャを合成する最小限のセットとソースコードを用意して検証します。合成する2枚のテクスチャは次の通りです。ご存じ「レナ」とグラデーションの画像、解像度は512x512になっています。
検証用の合成関数は次の通りに用意しました。記述方法は分かりやすさを優先しています。用意した2枚のテクスチャをHLSL内の変数、「TextureA(とSampler)」「TextureB」に与えて、対応する座標の色をそれぞれ乗算します。
float4 PixelShaderFunction(float2 TexCoord:TEXCOORD) : COLOR0 { float4 colorA = tex2D(TextureASampler,TexCoord); float4 colorB = tex2D(TextureBSampler,TexCoord); float4 color; //グレーの値を乗算する。正しい結果ならグラデーションがかかるハズ。 color.r = colorA.r * colorB.r; color.g = colorA.g * colorB.g; color.b = colorA.b * colorB.b; color.a = 1; return color; }
得られて欲しい正しい結果と、失敗した時の画像はそれぞれ次の通りです。失敗した場合には真っ黒の画面が表示されることが分かると思います。
NGパターン - 1
サンプルプロジェクトに含まれるEffect1は失敗するパターンです。レジスタ番号を指定せず、参照される(グラデーション)テクスチャを格納するための変数を先に定義しています。レジスタ番号を指定しない場合は宣言順に扱われるようです。よってここではTextureBのレジスタ番号が0番、TextureAのレジスタ番号が1番となり、TextureBに前もって与えていたテクスチャ情報は、SpriteBatchによってオブジェクトが描画されるとき、その情報に上書きされて消えてしまいます。
//NGパターン(1) //TextureBのRegisterIndexが0になる //参照されるTexture texture TextureB; sampler TextureBSampler = sampler_state { Texture = <TextureB>; }; //SpriteBatchから送られるTexture texture TextureA; sampler TextureASampler = sampler_state { Texture = <TextureA>; };
OKパターン - 1
先のNGパターン1では任意のレジスタ番号を指定しませんでした。サンプルプロジェクトに含まれるEffect2ではレジスタ番号を指定しているため、SpriteBatchによって利用されるレジスタ番号0と、参照するテクスチャを保存しておくためのレジスタ番号が一致しないため期待した通りの処理が行われます。
//OKパターン(1) //TextureBのRegisterIndexが1になる //参照されるTexture texture TextureB; sampler TextureBSampler:register(s1) = sampler_state { Texture = <TextureB>; }; //SpriteBatchから送られるTexture texture TextureA; sampler TextureASampler:register(s0) = sampler_state { Texture = <TextureA>; };
OKパターン - 2
ここでは問題の分かりやすさのためにあえて逆順で書いていましたが、サンプルプロジェクトのEffect3の様に、参照される側のテクスチャ(ここではTextureB)をSpriteBatchで利用されるテクスチャよりも後に記述していれば、自動で割り当てられるレジスタ番号も重複しないので正常に動作します。こちらの方が実装・記述方法としては自然です。ただこの仕組みを知らずにいると、後から何気なしに追記したテクスチャが上書きされることに気が付かずに延々間違った結果を得ることになりかねません(自身がそうなりました)。
//OKパターン(2) //TextureBのRegisterIndexが1になる //SpriteBatchから送られるTexture texture TextureA; sampler TextureASampler = sampler_state { Texture = <TextureA>; }; //参照されるTexture texture TextureB; sampler TextureBSampler = sampler_state { Texture = <TextureB>; };
NGパターン - 2
仮にTextureの定義順を正しく書いていても、任意のレジスタ番号を履き違えていれば問題は発生します。SpriteBatchを用いるEffectファイルを実装するときは、レジスタ番号0に注意するようにしておけば問題ないでしょう。サンプルプロジェクトのEffect4になります。
//NGパターン(2) //TextureBのRegisterIndexが0になる //SpriteBatchから送られるTexture texture TextureA; sampler TextureASampler:register(s1) = sampler_state { Texture = <TextureA>; }; //参照されるTexture texture TextureB; sampler TextureBSampler:register(s0) = sampler_state { Texture = <TextureB>; };
結論・Conclusions
HLSLでテクスチャ参照を利用する場合は、レジスタ番号に注意しましょう。