SpriteBatchとEffect
SpriteBatchにEffectを指定する
前回の投稿ではXNA Framework 4.0で新しくなったSpriteBatch.Beginメソッドの紹介と、任意のレンダーステートを使ったものを例として紹介しました。今回はSpriteBatch.BeginメソッドにEffectを設定する方法を紹介します。
前回も説明したとおり、Effectを設定する場合は以下の二通りのシナリオを考えて設計されています。
- 通常のスプライト描画で、主にピクセルシェーダーによってカスタマイズする
- View、Projectionなどを使用した3D効果
1のシナリオでは、例えば画面全体をセピア色に変更したりする単純なカラー操作や、画像を波打たせるような効果を表現したいときに有効です。
2のシナリオでは、スプライト自体を3Dの板として扱い、拡大縮小をはじめ、スプライトを任意の軸で傾けるといった効果を表現したいときなどがあります。
Effectを使った2D描画
SpriteBatch.Beginメソッドに指定した場合、SpriteBatchはスプライトの四角形の各頂点の生成は通常通り行いますが、2D描画用の行列計算は使用するEffect側で設定する必要があります。
Reachプロファイルではシェーダーがサポートされていないので、CTP版ではピクセルシェーダーを使えませんが(HiDefのリリースまで待ってね)、ここではBasicEffectを使って2D描画する例を紹介します。
// BasicEffectを生成し、必要なパラメーターを設定する
effect = new BasicEffect(GraphicsDevice);
effect.TextureEnabled = true;
effect.VertexColorEnabled = true;
Rectangle rc = GraphicsDevice.Viewport.Bounds;
// 2D描画用のプロジェクション行列を計算する
// XNA(DX9)のスクリーン座標は0.5ピクセルずれているので、
// その調整をするのを忘れずに
effect.Projection =
Matrix.CreateTranslation( -0.5f, -0.5f, 0) *
Matrix.CreateOrthographicOffCenter( rc.Left, rc.Right, rc.Bottom, rc.Top, 0, 100 );
effect.View = Matrix.Identity;
effect.World = Matrix.Identity;
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, effect);
spriteBatch.Draw(gridTex, new Vector2(150, 40), Color.White );
spriteBatch.End();
2D描画用の行列計算をするのにMatrix.CreateOrthographicOffCenterメソッドに現在のViewportの矩形を指定しています。また、XNA(DX9)のスクリーン座標は0.5ピクセルずれているので、CreateOrthgraphicOffCenterメソッドで作った行列だけを指定すると、常にピクセルがずれた状態になります。この状態を防ぐためにMatrix.CreateTranslationを使って半ピクセル元に戻すようにしています。
SpriteBatchで3D描画
Effectを指定して3D描画する場合は、通常の3D描画と同様にView、Projection、そしてWorld行列を使用することができます。3D描画の場合、Vector2ではなくVector3を使いますがSpriteBatchを使用して描画した場合、各頂点のXとY座標はDrawメソッドに渡された座標を使い、Z値にはlayerDepthに渡された値を使います。
ひとつ注意しないといけないのは、スクリーン座標と3D座標でY座標が上下反転しているということです。スクリーン座標ではY座標は画面の上の方がマイナスで下がプラスになっていますが、3D座標では画面の上の方がプラスで下がマイナスになります。ですから、普通に描画するとスプライトの上下が逆転してしまいます。結果的にポリゴンの描画方向が逆になるのでカリング対象になり、描画されていないように見えてしまいます。
対処方法としてはゲームによって変わってきます。例えば2Dゲーム部分がメインで3D効果が少しの場合はスクリーン座標でコーディングして、3D効果の部分をスクリーン座標に合わせた行列を生成するようにするようにする手法を使います。逆に2Dゲームだけど3D効果を多用したい場合は、全てのスプライト描画をワールド座標で行ってしまうという手法があります。
ここでは後者の方法を採用し、スプライトの描画はワールド座標で指定しています。また、描画方向逆転の問題は高さのパラメーターにマイナスの値を指定することで解決しています。
// BasicEffectを生成し、3D描画に必要な行列を設定する
effect = new BasicEffect(GraphicsDevice);
effect.TextureEnabled = true;
effect.VertexColorEnabled = true;
effect.EnableDefaultLighting();
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);
effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 13), Vector3.Zero, Vector3.Up);
effect.World = Matrix.CreateRotationX(MathHelper.ToRadians(-50.0f));
// エフェクトを指定して3D描画をする
// 通常の3D描画として扱うのでDepthStencilState.Defaultを設定する
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, DepthStencilState.Default, null, effect);
spriteBatch.Draw(texture,
new Rectangle(-5, 5, 10, -10), // 高さがマイナスになっていることに注意
null, Color.White, 0, Vector2.Zero, SpriteEffects.None,
0 // layerDepth値はZ値として使われる
);
spriteBatch.End();
このコードを実行すると、以下の画像のようにスプライトを画面奥に向けて傾けて描画することができます。
また、SpriteBatch.Beginを呼んでからDrawメソッドをEffectのWorld行列を変更しながら描画することもできます。以前はSpriteSortMode.Immediateを指定してもテクスチャが同じ場合はSpriteBatch.Endメソッドが呼ばれたときにまとめて描画するようになっていましたが、4.0ではSpriteBatch.Being/End間でEffectのパラメーターを変更しながら描画するというシナリオに対応する為にSpriteSortMode.Immediateを指定した場合はDrawを呼ぶごとに実際に描画するようになりました。
下の画面はGamefest 2010のデモ画面ですが、元のコードはプログラムで頂点バッファやインデックスバッファ、頂点宣言等を使用して3Dのポリゴンとして描画していました。ですが、ここでは3Dテキスト以外は全てSpriteBatchで描画するように変更しています。静止画だと判りづらいのですが、このシーンは複数の3Dのリング状に並んだ画像が回転しているものです。
また、Reachプロファイルでも使えるBasicEffectを使用しているので、まったく同じコードがWindows Phone 7 シリーズでも動作します。
まとめ
以上のようにXNA Game Studio 4.0ではSpriteBatchのカスタム描画が容易となり、今までは難しかった3D効果もで容易にできるようになりました。
で、以下はお約束の注意点です。
- スクリーン座標とワールド座標ではY軸が逆になっている
- 3D効果を使いたい場合は、使用する画像にミップマップを指定しないと、見た目が悪くなるのとパフォーマンス低下に繋がる場合があるので、できる限りミップマップを指定すること
- ただし、Reachプロファイルではミップマップは2の乗数のサイズのテクスチャに限られていることに注意(実行時エラーとなる)。元の素材のサイズを変更するか、テクスチャプロセッサーのパラメーターで2のn乗のサイズに変更するように指定する
- Effectに渡されるのは頂点座標とカラー、UV座標のみ。法線はEffectに渡されないのでライティング処理をする場合はシェーダー内で生成する必要がある(HiDefのみ)
Comments
Anonymous
January 14, 2012
SpriteBatchで3Dを表示するサンプルコードで表示を行おうとした時、 SpriteBatch.Draw() において -------------------------------------------、 InvalidOperationException はハンドルされませんでした。 現在の頂点宣言には、現在の頂点シェーダーに必要な要素が全て含まれていません。 Normal0 がありません。 -------------------------------------------、 となってしまいます。 こちらの動作環境が原因でしょうか? 2D表示につきましてはサンプルコードそのままで表示にも問題はありませんでした。Anonymous
January 16, 2012
この記事の注意点にあるよう、SpriteBatchが生成する頂点には頂点座標とカラー、UV座標のみです。法線を必要とするシェーダーを指定するとこの例外が発生します。頂点シェーダーの入力パラメーターから法線を取り除くことでこの例外は解決するはずですAnonymous
February 02, 2014
>> 頂点シェーダーの入力パラメーターから法線を取り除く これは、fxファイルを作成して行うことでしょうか? 具体的なコードを教えていただけないでしょうか?Anonymous
March 23, 2014
返事が遅れて済みません 自前のシェーダーを使う場合はfxファイルを作る必要がありますが、BasicEffectを使う場合はLightingEnabledプロパティにfalseを設定することでも使用することができますAnonymous
May 26, 2014
回答していただけるとは思っていませんでした。 いま確認しました。 effect.LightingEnabled = false; 1行追加したところ、 みごと動作しました。ありがとうございます。