Twitter @HigenekoTwitter #XNA
前回の投稿ではXNA Framework 4.0で新しくなったSpriteBatch.Beginメソッドの紹介と、任意のレンダーステートを使ったものを例として紹介しました。今回はSpriteBatch.BeginメソッドにEffectを設定する方法を紹介します。
前回も説明したとおり、Effectを設定する場合は以下の二通りのシナリオを考えて設計されています。
1のシナリオでは、例えば画面全体をセピア色に変更したりする単純なカラー操作や、画像を波打たせるような効果を表現したいときに有効です。
2のシナリオでは、スプライト自体を3Dの板として扱い、拡大縮小をはじめ、スプライトを任意の軸で傾けるといった効果を表現したいときなどがあります。
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を使って半ピクセル元に戻すようにしています。
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効果もで容易にできるようになりました。
で、以下はお約束の注意点です。
SpriteBatchクラスはXNA Frameworkの中で最も使用頻度の高いクラスで、特に2Dゲームでは必須の機能です。私達がXNA GSE 1.0でSpriteBatchをデザインしたときには、一般的な2Dスプライト描画ができることが主な目的でした。確かにSpriteBatchは通常の2Dスプライトを描画するには非常に便利な機能ですが、いくつかの要望がありました。
1についてですが、今までにSpriteBatch.Beginメソッドに指定できるSpriteBlendMode列挙には4つのブレンドモードしかなく、これ以外のブレンドモード、例えば色反転用のブレンドモードなどはサポートされていませんでした。
2については特に独自のピクセルシェーダーを使いたいときなどにカスタムエフェクトは有効な手段ですが、オンラインドキュメントの「スプライトへのピクセルシェーダーの適用」にあるように、直感的ではないコードを書く必要がありました。
3の3D効果についてですが今までのSpriteBatch.Beginメソッドには任意の行列を指定できるオーバーロードがありましたが、もともとは画面全体に対してスケーリングや回転などの処理をする時のために設計されたものなので、個々のスプライトを3D表示するのには向いていませんでした。
これらの要望を解決するために4.0ではSpriteBatch.Beginメソッドを変更しました。以下は4.0のSpriteBatch.Beginメソッドです。
以前あった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メソッドを読んだ後にレンダーステート設定するといった直感的でないコードを書く必要がなくなり、簡潔に書けるようになりました。
このコードを実行すると、以下のような結果になります。
今までと同じようにSpriteBatchはレンダーステートを変更します。今までは指定したSpriteBlendModeによって変更されるレンダーステートが変わったりするので、レンダーステートを手動で元に戻すのは面倒な作業でした。4.0ではレンダーステートオブジェクトのお陰でSpriteBatchによって変更されたレンダーステートを元に戻すのが容易になりました。
SpriteBatchによって変更されたレンダーステートは以下の三行のコードで元に戻すことができます。
GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
通常、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とカスタムエフェクトの使い方を紹介します。
XNA Game Studio 3.1では描画するときに欠かせないレンダーステート情報を変更するには、GraphicsDevice.RenderStateプロパティを介する必要がありました。プロパティ化したことにより、デバッガ上で即座にレンダーステートを確認できるというのはC++でDirectXを使っていた時よりも格段に使いやすくなっていました。
ですが、RenderStateクラスには実に70近いプロパティがあり、中には複数のプロパティを正しく設定しないと思い通りに描画できないといった問題があったり、Visual Studio上で編集しているときでもインテリセンスに表示される候補が多すぎるという問題がありました。また、パフォーマンス的にも複数のレンダーステートを設定するのは時間が掛かってしまいます。さらにコードのメンテナンスしやすさの面から見ても、これだけの量のレンダーステートを人力で管理するのは難しく、かといって安易にStateBlockを使用すると更なるパフォーマンス低下の原因になってしまうという問題がありました。
そこで、XNA Framework 4.0ではこれらの問題を解決する為に、レンダーステートを以下の4つのステートにカテゴリ分けしています。
また、GraphicsDeviceにはBlendFactorプロパティがあり、BlendState.BlendFactorと同じものです。これはBlendFactorを使用するBlendStateを設定した後にBlendFactorの値だけを変えたい時の為に用意されています。GraphicsDevice.ReferenceStencilとDepthStencilState.ReferenceStecilの関係も同じものです。
それぞれのレンダーステートはあらかじめオブジェクトとして生成する必要があります。例えばBlendStateの場合は以下のように生成します。
// 一般的な半透明ステートオブジェクトを生成するBlendState myState1 = new BlendState(); myState1.ColorSourceBlend = Blend.SourceAlpha; myState1.AlphaSourceBlend = Blend.SourceAlpha; myState1.ColorDestinationBlend = Blend.InverseSourceAlpha; myState1.AlphaDestinationBlend = Blend.InverseSourceAlpha;
もしくは、C# 3.0から使えるようになったオブジェクト初期化子を使用して
BlendState myState2 = new BlendState { ColorSourceBlend = Blend.SourceAlpha, AlphaSourceBlend = Blend.SourceAlpha, ColorDestinationBlend = Blend.InverseSourceAlpha, AlphaDestinationBlend = Blend.InverseSourceAlpha, };
のように書くこともできます。この書き方のが一行ごとにオブジェクト名を書かなくて良いのですっきりとした感じになります。
こうしてできたステートオブジェクトはGraphicsDeviceクラスのプロパティに設定することでレンダーステートを変更することができます。
GraphicsDevice.BlendState = myState1;
このままだと、レンダーステートを設定する時にあらかじめ使用するステートオブジェクト作らないといけないので、それらを管理する手間が増えてしまいます。確かに、その方がパフォーマンス的には有利なのですが、良く使われるレンダーステートくらいは直ぐに使いたいと思う人が多いでしょう。
そこで、それぞれのレンダーステートクラスには良く使われるステートの組み合わせが静的プロパティとしてあらかじめ用意されています。
BlendStateであらかじめ宣言されているBlendStateプロパティ
DepthStencilStateであらかじめ宣言されているDepthStencilStateプロパティ
RasterizerStateであらかじめ宣言されているRasterizerStateプロパティ
SamplerStateであらかじめ宣言されているSamplerStateプロパティ
これら四種類のレンダーステートオブジェクトにカテゴリ分けと、あらかじめ用意されているステートの組み合わせを使用することでレンダーステートの管理がしやすくなりました。どれぐらい簡単になったかというと、例えば全てのレンダーステートを初期値に戻したいという場合があったとします(普段はあまりないけれど)。このケースの場合は以下の数行を書き加えるだけで実現することができます。
// レンダーステートを初期値に戻すGraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; // サンプラーステート数はReach、HiDefともに16個 for (int i = 0; i < 16; ++i) GraphicsDevice.SamplerStates[i] = SamplerState.LinearWrap;
XNAチームは発足当初から他のMicrosoftのプロダクトチームに比べると非常に少ない人数です。人数的に見て他のチームがXNAチームの10倍の規模なんていうのは普通にあることで、時には100倍の人数規模のチームと仕事をするなんてこともあります。
私達がXNA Game Studio 1.0の時にFrameworkを設計したときにはXbox 360上で.Net CFを動くようになったのは最初のリリースの四ケ月程前でした。もちろん、たった四ケ月という期間と少ない人員でFrameworkの設計、実装、そしてテストをすることはできないので、Xbox 360上で.Net CFが動くまでの間に平行して他の作業を進めました。
この時点で私達が設計、実装ができたのはWindows上のみだったので、自然とManaged DirectXの設計を参考にする機会が多くなり、どちらかというとDirectX 9.0のラッパーのようなものが多くなりました。
この方針は設計期間を短くすることに繋がったのですが、後になってXbox 360とWindows上の動作に違い、例えばRenderTarget2Dの振る舞いの違いなどがあることに気づいた時には既にAPIの仕様を変更するには遅すぎる時期になってしまい、結局はXbox 360とWindowsの動作の違いはそのままにすることになってしまいました。
また、追加してしまつた機能を削除するのは、新しく機能を追加するよりも遥かに難しいということを実感したのもXNA Game Studio Express 1.0をリリースした後になってからでした。
一度追加してしまった機能は、使用頻度が少なくとも削ってしまうと、それまで動作していたゲームが動かないということになってしまいます。この使用頻度が低いAPIというのが厄介で、いかに使用頻度が低くてもAPIとして提供している以上はそのAPIが動作しているかどうか確認するためのテストをする必要があります。そして、前述のように人員の少ないXNAチームでは、このテストに掛かる時間が多くなってしまい、そのことで新機能を追加する時間が少なくなってしまうというのは問題です。
XNA Game Studio 4.0でWindows Phone 7シリーズへの対応が決まった時、私達はこれがリファクタリングをするにはいい機会だと考えました。
特にグラフィクスに関しては今までスマートフォーン向けのGPU用のDirectXドライバは存在せず、ドライバの設計はもちろん、ハードウェアベンダーとの話し合いからすることができた(しなければいけなかった)ので、XNAチーム側からの意見の多くが採用されました。
ここで私達は今まで経験から、できるだけ長い間使えるAPIになるようにDirectX10 APIを元にしたAPIへとリファクタリングをすることにしました。と、言うと「ジオメトリシェーダーが使える!」と、考える人がいると思いますが、残念ですがXNA Game Studio 4.0の段階でDirectX 10特有の機能はサポートしていません。
XNA Game Studio 4.0ではDirectX 9世代のGPUもサポートしているので、DirectX 10 APIをDirectX 9世代のGPU上で使用する、D3D10Level9と同等のことをしています。
XNA Game Studio 4.0のグラフィクスではこの他にも以下のようなリファクタリングが施されています。
次回は新しくなったレンダーステートの紹介をします。
Windows Phone 7 シリーズでアプリケーションを作る場合は、SilverlightとXNA Frameworkを使うことができます。SilverlightはMicrosoft Expression Blendとの連携で簡単にUI構築ができるという利点があり、XNA Frameworkは3Dを筆頭に高パフォーマンスが求められるゲームアプリに向いています。
Silverlightでアプリケーションを作っているときにXNA フレームワークの機能、特に追加されたばかりのダイナミックオーディオ機能や、GamerServices機能を使いたいというケースがあります。
このことを実現するために、4.0ではアセンブリファイルが機能ことに細分化され、それぞれが独立して機能するように変更が加えられています。
XNA Game Studio 3.1までは実行時に必要なアセンブリファイルはMicrosoft.Xna.Framework.dllとMicrosoft.Xna.Framework.Game.dllの二つでしたが、4.0のReachプロファイルでは以下の様に各機能毎に分割されています。
これらのアセンブリファイルはMicrosoft.Xna.Framework.dllへの依存する以外はそれぞれ独立したアセンブリとして使用することができます。これで前述のように、SilverlightとXNA Frameworkの機能を組み合わせて使えるようになっています。同様にWindows上でもWPFやWinForm用のプロジェクトから、XNAのそれぞれのアセンブリを自由に使用することができるようになっています。
このアセンブリの細分化を言い換えると、Microsoft.Xna.Framework.Graphics.dll以外のアセンブリはからグラフィクスへの依存がなくなったとも言えます。
この副作用として、以前はTexture2DだったものがStreamを返すように変更されています。例えばGamerProfile.GamerPictureプロパティやメディアライブラリ内のPicture.GetTextureメソッドはGetImageメソッドに名称変更されているというようにです。
ここで���されるStreamは.Netフレームワークで読み込むことのできる一般的な画像フォーマットファイルとなっているので、SilverlightやWPF上で簡単にDataBinding先として使用することができます。
逆にこのストリームデータをゲーム内でテクスチャとして使うためにTexture2D.FromStreamメソッドが追加されています。
昨日のMIXのキーノート内で発表された通り、Windows Phone開発用ツールのCTP版がダウンロードできるようになりました。
Windows Phone開発者用ページ http://developer.windowsphone.com/
Windows Phone開発者用ツールCTP版ダウンロードページ http://www.microsoft.com/downloads/details.aspx?FamilyID=2338b5d1-79d8-46af-b828-380b0f854203&displaylang=en
リリースノート http://download.microsoft.com/download/D/9/2/D926FB38-BB43-4D87-AE5A-1A3391279FAC/ReleaseNotes.htm
このツール群の中にはXNA Game Studio 4.0 CTPを使うために必要な以下のものが含まれています。
XNA Game Studio 4.0として使う場合の注意点
今回公開されたのはReach、HiDefのプロファイルのうちのReachプロファイル部分のみとなっていることに注意してください。これに加えて以下のことに注意する必要があります。
Microsoft Visual Studio 2010 Express for Windows Phoneを起動して、プロジェクトの新規作成をすると以下の画面が表示されます。おなじみのWindows Game, Xbox 360 Gameのテンプレートがあるのはもちろん、Windows Phone Gameテンプレートが追加されています。
以前はゲームプロジェクトのサブプロジェクトとして存在していたコンテントプロジェクトが独立したプロジェクトとなりました。これにより、複数のプラットフォーム間でのコンテントの共有がしやすくなりました。
下の画面はWindows Phone Emulator上で3Dモデルを読み込んで表示させたものです。Content.Load<Model>でモデルを読み込み、ModelMesh.Drawで描画するといった手法は今までどおり使えます。もちろん、MatrixやVector3といったMathライブラリはまったく同じ物を使うことができるので、この画面を表示するまでに数分しか掛かりませんでした。
次からは、XNA Game Studio 4.0になって変更された部分についての詳細を紹介していきます。
最近、私の忙しさの尺度はこのブログの更新頻度に反比例しているということに気づきました。去年の秋あたりから忙しくなり、今年に入ってGamefest、GDC、そしてMIXへ向けての作業に追われる毎日でした。で、ちょっとだけひと段落したので、またいろいろと記事を書いていこう思っています。
まず最初のニュースはXNA Game Studio 4.0についてです。XNA Game StudioはWindows、Xbox 360、そしてZuneと対応プラットフォームを増やしてきましたが、4.0ではWindows Phone 7シリーズへの対応が決まりました。
Windows Phone 7シリーズでは要望の多かった3D APIが追加されています。ただし、Xbox 360やWindowsなどと比べると非力な携帯デバイスなので、複数のプラットフォームでゲームを作る場合にはプラットフォーム間のパフォーマンス差を考慮する必要があります。
この労力を軽減するために4.0では、”Reach(リーチ)”と"HiDef(ハイデフ)”の二つのCapsを設けました。ReachはWindows Phone 7シリーズを含めたどのプラットフォームでも動作させることのできるAPI群からなり、HiDefはXbox 360やハイエンドWindowsマシン上で動作することを前提にしたAPI群からなります。HiDefではReachの全APIが使えるようになっています。
以下はXNA Game Studio 4.0の主な新機能です。
これらの詳細は今週開催されているGDC、そして来週開催されるMIXで公開される予定です。
http://blogs.technet.com/microsoft_blog/archive/2010/03/09/game-developers-have-a-great-opportunity-with-windows-phone-7-series.aspx
http://blogs.msdn.com/shawnhar/archive/2010/03/09/in-which-hints-become-facts-xna-game-studio-4-0.aspx#comments
http://klucher.com/blog/achievement-unlocked-xna-game-studio-4-0-for-windows-phone/