Compartilhar via


SpriteBatchとレンダーステート

SpriteBatchをカスタマイズしたい

SpriteBatchクラスはXNA Frameworkの中で最も使用頻度の高いクラスで、特に2Dゲームでは必須の機能です。私達がXNA GSE 1.0でSpriteBatchをデザインしたときには、一般的な2Dスプライト描画ができることが主な目的でした。確かにSpriteBatchは通常の2Dスプライトを描画するには非常に便利な機能ですが、いくつかの要望がありました。

  1. アルファブレンドをはじめとしたレンダーステートを自由に設定したい
  2. カスタムエフェクトと併用したい
  3. 3D効果が使いたい

1についてですが、今までにSpriteBatch.Beginメソッドに指定できるSpriteBlendMode列挙には4つのブレンドモードしかなく、これ以外のブレンドモード、例えば色反転用のブレンドモードなどはサポートされていませんでした。

2については特に独自のピクセルシェーダーを使いたいときなどにカスタムエフェクトは有効な手段ですが、オンラインドキュメントの「スプライトへのピクセルシェーダーの適用」にあるように、直感的ではないコードを書く必要がありました。

3の3D効果についてですが今までのSpriteBatch.Beginメソッドには任意の行列を指定できるオーバーロードがありましたが、もともとは画面全体に対してスケーリングや回転などの処理をする時のために設計されたものなので、個々のスプライトを3D表示するのには向いていませんでした。

刷新されたSpriteBatch.Beginメソッド

これらの要望を解決するために4.0ではSpriteBatch.Beginメソッドを変更しました。以下は4.0のSpriteBatch.Beginメソッドです。

SpriteBatch.Begin ()
SpriteBatch.Begin (SpriteSortMode, BlendState)
SpriteBatch.Begin (SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState)
SpriteBatch.Begin (SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect)
SpriteBatch.Begin (SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix)

以前あったSpriteBlendModeやSaveStateModeが消えた代わりに、レンダーステートオブジェクトと任意のEffectが指定できるようになりました。ちなみにSpriteSortModeとMatrix以外のパラメーターは全てnullを指定することでき、nullを指定した場合は規定値を設定するようになっています。

引数の無いSpriteBatch.Beginメソッドを使用した場合、以下のコードと同様になります。

 // spriteBatch.Begin()を読んだ場合、以下のコードと同じ意味を持つ
spriteBatch.Begin(
    SpriteSortMode.Deferred,
    BlendState.AlphaBlend,
    SamplerState.LinearClamp,
    DepthStencilState.None,
    RasterizerState.CullCounterClockwise
    );

任意のレンダーステートを指定する

このようにレンダーステートオブジェクトをSpriteBatch.Beginメソッドに直接指定できるようになったので、任意のブレンドステートを容易に指定できるようになりました。

例えば色反転をするブレンドステートを使いたい場合は、以下のコードのようになります。

 // 普通にスプライトを二つ並べて描画する
spriteBatch.Begin();
spriteBatch.Draw(tex, new Rectangle(0, 100, 400, 400), Color.White);
spriteBatch.Draw(tex, new Rectangle(400, 100, 400, 400), Color.White);
spriteBatch.End();

// 色反転用のブレンドステートを生成する
BlendState invColorBlend = new BlendState
{
    ColorBlendFunction = BlendFunction.Subtract,
    AlphaBlendFunction = BlendFunction.Subtract, 
    ColorDestinationBlend = Blend.One,
    AlphaDestinationBlend = Blend.One,
};

// 色反転用のブレンドステートを指定して、右側画像の色を反転する
spriteBatch.Begin(SpriteSortMode.Deferred, invColorBlend);
spriteBatch.Draw(whiteTex, new Rectangle(400, 100, 400, 400), Color.White);
spriteBatch.End();

いままでのようにSpriteSortMode.Immideateを指定して、Beginメソッドを読んだ後にレンダーステート設定するといった直感的でないコードを書く必要がなくなり、簡潔に書けるようになりました。

このコードを実行すると、以下のような結果になります。 the-world

 SpriteBatchとレンダーステート

今までと同じようにSpriteBatchはレンダーステートを変更します。今までは指定したSpriteBlendModeによって変更されるレンダーステートが変わったりするので、レンダーステートを手動で元に戻すのは面倒な作業でした。4.0ではレンダーステートオブジェクトのお陰でSpriteBatchによって変更されたレンダーステートを元に戻すのが容易になりました。

SpriteBatchによって変更されたレンダーステートは以下の三行のコードで元に戻すことができます。

 GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

なぜLinearClampなのか?

通常、3Dモデルを表示する場合、テクスチャのアドレッシングモードはWrapを使用します。これはテクスチャを連続して並べるようにして表示することができからです。実際、SamplerStateの規定値はLinearWrapになっています。

ではなぜSpriteBatchではLinearClampを設定しているのでしょうか?

これはReachプロファイルで対応しているハードウェアの制限から来ています。Reachプロファイル対象となるGPUには2の乗数ではないサイズ(2,4,8,16,32,64,128,256,512…といったサイズ以外)のテクスチャにはWrapを指定できないという制約があります。

タイトル画面の画像をはじめとして、SpriteBatchを使う場合は任意のサイズの画像を使用するケースが多くなります。もし、SpriteBatchで指定する規定値がLinearWrapになっていた場合、「タイトル画面は1280x720の画像を表示しよう」というときにReachプロファイルでは実行時にエラーとなってしまう問題があります。

以上の理由からSpriteBatchのSamplerStateはLinearClampを指定するようになっています。

次回は新しくなったSpriteBatchとカスタムエフェクトの使い方を紹介します。