Welcome to MSDN Blogs Sign in | Join | Help

XNA Game Studio 3.1のSoundEffect変更点

XNA Game Studio 3.1のSoundEffect

ゲームで使用する効果音を簡単に再生するためにXNA Game Studio 3.0で導入されたSoundEffect機能がありますが、この機能は以下の二つのシナリオを実現するようにデザインされました。

  • 簡易再生(Fire & forget): プログラムは単にPlayメソッドを呼ぶだけ。後は効果音が鳴り終わった後に自動的にメモリ開放処理をしてくれる機能
  • 生成と設定(Create & configure): 効果音を鳴らしている間に、音量、再生ピッチ、パンなどのさまざまなパラメーターを自由に設定できる機能。細かい設定ができる代わりに、プログラム側で再生中のインスタンスの保持、管理をする必要がある

この二つのシナリオを実現する為に、XNA GS 3.0ではSoundEffect.PlaySoundEffect.Play3DメソッドはSoundEffectInstanceオブジェクトを返すようになっていました。再生中に設定を変更したい場合は、このインスタンスに対して処理をします。簡易再生させたい場合はインスタンスを保持しないでおきます。この場合、次にガーベージコレクション(GC)が発生した時に再生が終了したインスタンスを破棄するようになっていました。

この方法には以下の二つの問題がありました。

  • Play/Play3Dメソッドには、プログラムが簡易再生をしたいのか、細かい設定をしたいのかを知る術がないので常にSoundEffectInstanceを生成していました。これは簡易再生をしている場合に常に不必要なメモリ確保が起きていることになります。
  • 確保されたSoundEffectInstanceはGCが発生された時に再生が終了しているインスタンスを自動的に破棄されるようになっています。GCがいつ発生するかはゲームがどのようにメモリを確保するかによって左右されます。GCの不必要な発生はゲームのパフォーマンスに影響するので、極力GC発生を抑えるようにプログラミングするのが望ましいのですが、GC発生数が少ないと、SoundEffectinstanceを開放する機会が減るのことになるので、最終的には同時再生できる効果音の上限に達してしまいます。

XNA GS 3.1では、この二つのシナリオを明確に分けることで、この問題を解決しました。

  • SoundEffect.Playメソッドは簡易再生専用のメソッドとなり、SoundEffectInstanceではなく、bool値を返すようになりました。このbool値は単純に再生が成功したかを示します。これで不必要なメモリ確保がなくなりました。また、このメソッドを使った場合はループ再生ができないようになっています。
  • 生成と設定のシナリオ用にSoundEffect.CreateInstanceメソッドが追加されました。このメソッドはSoundEffectInstanceを返すので、このオブジェクトを介して音量、ピッチ、パンなどを設定することができます。

SoundEffect.Play3Dメソッドは無くなりました。3Dサウンドを再生する場合は、SoundEffect.CreateInsntaceメソッドを使い、SoundEffectInstance.Apply3Dメソッドを使用します。3Dサウンドは簡易再生することができません。

また、XNA GS 3.0では再生数が上限に達したときに更に効果音を再生しようとするとInstancedPlayLimitException例外が発生しました。XNA GS 3.1でもCreateInstanceメソッドを使った場合には同じように例外が発生しますが、簡易再生の場合は例外を発生せずに単にbool値を返すようになっています。ですから、簡易再生時にいちいちtry~catchブロックを書く必要がなくなりました。

原文:
http://blogs.msdn.com/shawnhar/archive/2009/06/12/soundeffect-changes-in-xna-game-studio-3-1.aspx

アバターを使おう その3:喜怒哀楽

警告:画像が沢山あるので、重いかも。

アバターの表情(Expression)を自由に設定する

AvatarRenderer.Drawメソッドにはアバターのボーンと表情を指定することができます。基本アニメーションを使っている場合はAvatarAnimation.Expressionを指定しますが、ボーンと同じく、AvatarExpressionも自由に作ることができるので、アバターの表情を自由に変えることができます。

AvatarExpressionには以下のプロパティがあります。

  • LeftEye (左目)
  • LeftEyebrow (左眉)
  • Mouth (口)
  • RightEye (右目)
  • RightEyebrow (右眉)

LeftEye、RightEyeにはAvatarEye列挙型、LeftEyebrow、RightEyebrowにはAvatarEyebrow列挙型、MouthにはAvatarMouth列挙型を設定することができます。

左右の目や眉に独立した値を設定できるので、ウィンクさせるといったことも可能です。眉と目、そして口のパターンと合わせると約6万9千通りの表情を設定することができます。これだけの数があるので、喜怒哀楽といった基本的な表情の他にも多彩な表情を表現することができるようになっています。

以下はそれぞれの列挙型の値と、その説明です。

AvatarEyebrow列挙型

説明

Angry 怒り、眉を吊り上げる
Confused 困惑、眉を曲げる
Neutral 自然な眉のかたち
Raised 眉をあげる。驚いたときなどに使える
Sad 悲しみ、眉がさがる。眠たい表情にも使える

AvatarEye列挙型

説明

Angry 怒り、目がつりあがっている
Blink まばたき、目を閉じている状態
Confused 困惑、より目、または見上げている感じ
Happy ハッピー、目が大きくひらき、目尻が下がっている
Laughing 笑い、目を細めている
LookDown 下を見る
LookLeft 左を見る
LookRight 右を見る
LookUp 上を見る
Neutral 自然な目の形
Sad 悲しみ、目尻が下がる
Shocked 驚き、目が点になる
Sleeping 眠り、目を閉じている
Yawning あくび、眠そうな目

AvatarMouth列挙型

説明

Angry 怒り、歯をくいしばってる感じ
Confused 困惑、口がゆがめている
Happy ハッピー、口の両端が上がっている
Laughing 笑い
Neutral 自然体、すこし口が開いている
PhoneticAi リップシンク用、英語のAの発音、日本語の「あ」と「え」の中間
PhoneticDth リップシンク用、英語のDまたはThの発音、唇を歯で軽く噛んで発音、日本語では使われない
PhoneticEe リップシンク用、英語のEの発音、日本語では「い」の発音
PhoneticFv リップシンク用、英語のF、Vの発音、下唇を噛んで発音、日本語では使われない
PhoneticL リップシンク用、英語のLの発音、舌のを歯のうらに当てて発音、日本語の「れ」に近い
PhoneticO リップシンク用、英語のOの発音、日本語では「お」の発音
PhoneticW リップシンク用、英語のWの最後の部分の発音、日本語では「う」の発音
Sad 悲しみ、口の両端が下がっている
Shocked 驚き、「あ」と口をあけて驚いている

百聞は1スクショにすぎず

前回の基本アニメーションと同じく、男性、女性アバターにいろいろな表情を設定したものを画像にしてみました。

まずは、眉、目、口で共通するものが多い表情パターンです。

myxbox-72自然な表情(Neutral)
Eyebrow.Neutral
Eye.Neutral
Mouth.Neutral

myxbox-74怒り(Angry)
Eyebrow.Angry
Eye.Angry
Mouth.Angry

 

myxbox-75

 

困惑(Confused)
Eyebrow.Confused
Eye.Confused
Mouth.Confused

myxbox-76笑い(Laughing)
Eyebrow.Neutral
Eye.Laughing
Mouth.Laughing

myxbox-78

ハッピー(Happy)
Eyebrow.Neutral
Eye.Happy
Mouth.Happy

myxbox-73

悲しみ(Sad)
Eyebrow.Sad
Eye.Sad
Mouth.Sad

myxbox-77

 

驚き(Shocked)
Eyebrow.Raised
Eye.Shocked
Mouth.Shocked

 

つづいて、目特有の表情パターンです。上下左右を見るというパターンがあるので、画面内の物を目で追うといったこともできます。

myxbox-85まばたき(Blink)
Eyebrow.Neutral
Eye.Blink
Mouth.Neutral

myxbox-79

 

あくび(Yawning)
Eyebrow.Neutral
Eye.Yawning
Mouth.Shocked

myxbox-80
ねむり(Sleeping)
Eyebrow.Neutral
Eye.Sleeping
Mouth.Happy

myxbox-81
上を見る(LookUp)
Eyebrow.Neutral
Eye.LookUp
Mouth.Neutral

myxbox-82
下を見る(LookDown)

Eyebrow.Neutral
Eye.LookDown
Mouth.Neutral

myxbox-83左を見る(LookLeft)
Eyebrow.Neutral
Eye.LookLeft
Mouth.Neutral

myxbox-84
右を見る(LookRight)

Eyebrow.Neutral
Eye.LookRight
Mouth.Neutral

 

最後に口特有の表情パターンです。表情パターンというよりも、リップシンク用のデータです。プレイヤーが操る以外のアバターには自由に喋らせることができるので、その時に使うパターンです。

myxbox-87
PhoneticAi

myxbox-92
PhoneticDth

myxbox-88
PhoneticEe

myxbox-89
PhoneticFv

myxbox-86
PhoneticO

myxbox-91  PhoneticL

myxbox-90 
 PhoneticW

 

リップシンク用のデータはもともと欧米向けのデータなので、日本語を喋らせるときには近い口の形を流用することになります。以下は日本語で「あいうえお」の口の形に近いものを順に並べてみました。

myxbox-93 あ (Shocked)

myxbox-88い (PhoneticEe)

myxbox-90う (PhoneticW)

myxbox-87え (PhoneticAi)

myxbox-86

お (PhoneticO)

 

アバターを使おう その2:基本アニメーション

アバターの基本アニメーション

AvatarAnimationを生成するときに指定するAvatarAnimationPreset列挙型には31種類の基本アニメーションが宣言されています。内訳として、11種類の男女共通のアニメーションと、性別毎に違うアニメーションが10種類となっています。

男性アバター向けのアニメーションはMaleで始まり、女性アバター向けのアニメーションはFemaleで始まるようになっています。通常はAvatarDescription.BodyTypeによって性別ごとにアニメーションを切り替えて使用しますが、アニメーションは性別に関係なく使えるので、意図的に男性アバターに女性アバターのアニメーションを再生させるということもできます。

男女共通のアニメーション

以下は男女共通アニメーション名と、その説明です。

アニメーション名

説明

Stand0 基本姿勢アニメーション、身体的アニメーションは殆どなく、瞬きする程度。
Stand1 基本姿勢アニメーションに加えて、数秒間軽く上を見上げる動作。
Stand2 基本姿勢アニメーションに加えて、左、上方向に顔を向ける動作。
Stand3 基本姿勢アニメーションに加えて、左右を見渡した後に軽く首をかしげる動作。
Stand4 軽く右足を上げて、つま先を見つめる。
Stand5 両手を広げて、前後に振る。
Stand6 足元を見回す動作。
Stand7 頭を左右に傾けた後に、首を回す動作。
Clap 笑顔で拍手する。
Wave 笑顔で手を振る。
Celebrate 喜びの動作。アバター生成画面でアバターを選択したときにするアニメーションと同じもの。

性別によって違うアニメーション

以下は性別によって異なるアニメーションをまとめた表です。殆どのアニメーションはMaleLaugh、FemaleLaughといった感じで同じタイプのアニメーションが用意されています。例外としてはMaleIdleCheckHand,FemaleIdleCheckNails,MaleIdleStretch,FemaleIdelFixShoeの四つのアニメーションがあります。

同じタイプのアニメーションでも、例えば笑うアニメーションの場合、男性はひざを叩いて笑う動作をするのに対して、女性は手を前にかざして笑うといった違いがあります。

男性用アニメーション名

女性用アニメーション名

説明

MaleIdleCheckHand FemaleIdleCheckNails 男性は肩を軽くまわした後に自分の手を見つめる動作。
女性は爪を見つめる動作。
MaleIdleLookAround FemaleIdleLookAround あたりを見回す動作。
FemaleIdleShiftWeight MaleIdleShiftWeight 重心を左右の足に移動する動作。
MaleIdleStretch FemaleIdleFixShoe 男性はストレッチ動作、女性は靴の様子を見る動作。
MaleAngry FemaleAngry 怒る。
MaleConfused FemaleConfused 困惑。
MaleLaugh FemaleLaugh 大笑い。
MaleCry FemaleCry 泣く。
MaleSurprised FemaleShocked 驚き。
MaleYawn FemaleYawn あくび。男性は手を口に当ててあくび。女性は手を広げてあくび。


百聞は一動画にしかずということで、全ての基本アニメーションを再生した動画を作ってみました。男性、女性アバターを並べて、それぞれのアニメーションを二回再生するようになっています。

3.1用プロジェクトへのアップグレード

XNA Game Studio 3.1ではXNA Game Studio 3.0のプロジェクトを3.1用にアップグレードすることができます。3.0で作ったプロジェクトをVisual Studioで開き、ソリューション・エクスプローラーで更新したいソリューションを右クリックで表示されるメニューの「Upgrade Solution…」を選択すると、3.1のプロジェクトへアップグレードします。

image

一旦3.1へアップグレートした後は、3.0のプロジェクトにダウングレードする方法は提供されていないので、3.0のプロジェクトを何らかの理由で残しておきたい場合は更新する前にバックアップしておくといいでしょう。

今まで紹介してきた以下のサンプルをXNA Game Studio 3.1用のプロジェクトにアップグレードしたので、以下に記事と新しいプロジェクトへのURLをまとめておきました。

基本的にはプロジェクトの更新のみですが、ソースコードファイルのエンコーディングをシフトJISからUnicode (UTF-8 シグネチャ (BOM) 付き) - コードページ 65001へ変更してあります。これはサンプルプログラムを日本語以外の環境で開くとコンパイルできないという問題を解決するためのものです。Visual Studioやメモ帳などのアプリケーションではUTF-8に対応しているので問題はありませんが、対応していないエディターでコードを開くときには日本語部分が正しく表示されないので注意してください。

vFetchでスキンアニメーション

http://higeneko.net/hinikeni/sample/xna31/vFetchSkinningSample.zip

頂点テクスチャでスキンアニメーション

http://higeneko.net/hinikeni/sample/xna31/TexSkinningSample.zip

クォータニオンでスキンアニメーション

http://higeneko.net/hinikeni/sample/xna31/QuatSkinningSample.zip

デバッグサンプル

http://higeneko.net/hinikeni/sample/xna31/DebugSample.zip

Gamefest Japan 2008 デモプログラム

http://higeneko.net/hinikeni/sample/xna31/GamefestJapan2008Demo.zip

簡単(かもしれない)日本語表示

http://higeneko.net/hinikeni/sample/xna31/SimpleMessage.zip

Content Pipeline その3 そのカスタマイズ(日本語表示サンプル)

http://higeneko.net/hinikeni/sample/TextMessageSample31.zip

Posted by Yuichi Ito | 0 Comments
Filed under: ,

アバターを使おう その1:アバターを表示する

アバターをゲーム画面内に表示する

アバターをゲームt画面内に表示するには以下の三つのクラスが重要な役割を果たします。

  • AvatarDescription
  • AvatarAnimation(必ずしも必要ではない)
  • AvatarRenderer

AvatarDescription

AvatarDescriptionはアバターの性別、容姿、服装といった情報を保持しています。AvatarDescriptionのプロパティには男性(Male)、女性(Female)を表すBodyType、メートル単位の身長を表すHeightがあります。AvatarDescriptionを得るには以下の三つの方法があります。

  1. SignedInGamer.Avatarからゲーマータグのアバター情報を取得する
  2. AvatarDescription.CreateRandomメソッドを使ってランダムにアバターを生成する
  3. AvatarDescription(byte[] data)コンストラクタを使って生成する

1はゲーマータグに関連付けられたらアバター情報をSignedInGamer.AvatarからAvatarDescriptionを取得して使う方法で、自分のアバターをゲーム内で使う方法です。

2の手法は、自分のアバター以外をゲーム内で使ったり、複数の違った体形のアバターを自分のゲーム内で表示しても問題がないようにテスト目的として使う方法です。

3の方法は、アバター情報をネットワークを介して転送する時に使用します。実際にはByte[]型であるAvatarDesciprion.Descriptionプロパティの情報をネットワークを介して転送し、転送されたデータをAvatarDescription(byte[])コンストラクタに渡すことで同じアバターを生成することができます。

また、2の方法ではランダムにアバターを生成しますが、ゲームの雰囲気にあったアバターを自由に作りたいという時があります。この場合、ダッシュボード上で仮のゲーマープロファイルを生成、好きなアバターを編集します。そして、作ったアバターでサインインした状態でXNA Game Studioを起動、デバッガ上で該当するアバターのAvatarDescription.Descriptionプロパティの値を保存しておくことで自分の好きなタイプのアバターをゲーム内で使うことができます。

AvatarAnimation

XNA GS 3.1ではアバター用に31種類のアニメーションが用意されています。このアニメーションを再生する為に使うのがAvatarAnimationクラスです。AvatarAnimationクラスには現在のアニメーション再生位置、ボーン情報、表情(Expression)などの情報などが含まれます。AvatarAnimationクラスはAvatarAnimationPreset列挙型を指定して生成、通常はAvatarAnimation.Updateメソッドを使用してアニメーション再生をすることができます。

また、AvatarAnimation.CurrentPositionプロパティは現在のアニメーション再生位置を取得することに使いますが、任意の再生位置を設定することで自由にアニメーションすることもできます。

AvatarRenderer

アバターをゲーム画面に描画するにはAvatarRendererクラスを使用します。AvatarRendererクラスはBasicEffectクラスとや同じように描画するアバターのWorld、View、Projection行列や、環境光源(AmbientLightColor)や平行光源(LightDirection, LightColor)を指定することができます。BasicEffectと違うところは描画するときにアバターのボーン情報や表情を指定できることです。通常はAvatarAnimationのBoneTransformsExpresssionプロパティを指定します。

また、指定するボーン情報はIList<Matrix>型なので、自分で作った自由なアニメーションを設定することができます。アバターのボーン数は71あり、これ以外の数のボーンを指定すると例外が発生することに気をつけてください。カスタムアニメーションサンプルは近日中にクリエーターズ・クラブのサイトで公開予定です。

サンプルコード

アバターはGamerServicesを利用するので、アバター機能を使うときにはGamerServiceComponentを追加するのを忘れないようにしましょう。忘れた場合はGamerService機能を初期化することを促す例外が発生します。

Components.Add(new GamerServicesComponent(this));

次にアバターを使うのに必要なオブジェクトを生成します。

// アバター情報をランダムに生成する
avatarDescription = AvatarDescription.CreateRandom();

// アバターアニメーションの生成
avatarAnimation = new AvatarAnimation(AvatarAnimationPreset.Celebrate);

// アバター描画用のオブジェクトの生成
avatarRenderer = new AvatarRenderer(avatarDescription);

次にゲームのアップデート内でアニメーションを以下のようにして更新します。

avatarAnimation.Update(gameTime.ElapsedGameTime, true);

最後にAvatarRendererにアバターを描画するのに必要な行列を設定し、ボーンと表情を指定することでアバターをゲーム画面内に描画することができます。

// アバター描画に必要な行列の設定
avatarRenderer.World = Matrix.CreateRotationY(MathHelper.ToRadians(180.0f));
avatarRenderer.Projection = 
    Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(30.0f),
    GraphicsDevice.Viewport.AspectRatio,
    1, 1000);
avatarRenderer.View = Matrix.CreateLookAt(
    new Vector3(0, 1, 4),
    new Vector3(0, 0.7f, 0),
    Vector3.Up);

// ボーンと表情を指定してアバターを描画する
avatarRenderer.Draw(avatarAnimation.BoneTransforms, avatarAnimation.Expression);

上記のコードを実行すると、以下のようにアバターが画面に表示されます。このコードではランダムにアバターを作っているので実行するたびに違うアバターが表示されます。

itoxe1-78

アバターを使う上での注意点

  • GamerServiceComponentを追加するのを忘れないこと
  • 現状でアバターはXbox 360上でのみ動作するようになっています。ただし、アバター関連のコードをWindows上で走らせても例外が発生することなく動作します。
  • ゲーム内で使用するときには、背の高いのから低いのまで、痩せ型から太り気味といったさまざまなタイプのアバターが存在居るということに気をつける必要があります。例えば、アバターを矩形内に表示させたい時に身長の低いアバター向けに調整してしまうと、背の高いアバターを表示させると頭の上が途切れてしまうという問題があります。

参考URL:

http://blogs.msdn.com/dejohn/archive/2009/05/08/avatar-api-preview-for-xna-game-studio-3-1.aspx

http://blogs.msdn.com/dejohn/archive/2009/05/11/what-to-do-while-an-avatar-loads.aspx

http://blogs.msdn.com/dejohn/archive/2009/05/18/lights-camera-avatar.aspx

http://blogs.msdn.com/dejohn/archive/2009/05/20/avatardescription-isvalid-when-why-the-description-returned-is-invalid.aspx

http://blogs.msdn.com/dejohn/archive/2009/05/28/avatardescription-description.aspx

自動XNBファイルシリアライズ

コンテントタイプライター/リーダーを書くのは面倒

XNA Game Studioのコンテント・パイプラインでのデータの流れは下図のようになっています。オフラインプロセスは開発しているVisual Studio上でビルドしたときにWindows上で処理されるプロセスで、オフラインプロセスはWindows、Xbox 360、Zune上でゲームを実行したときに処理されるプロセスです。

cp-001

元からサポートされているデータをそのまま扱う場合には良いのですが、カスタムデータを扱う場合に書かないといけないのが、ContentTypeWrite/ContentTypeReaderでした。特に面倒だったのが、読み込みと書き込みという対称的な処理をするのにContentTypeWriterはオフラインプロセス側、ContentTypeReaderはオンラインプロセス側と別々の場所で書かないといけないことでした。

この面倒臭さを解決する為に、XNA GS 3.1では自動XNBシリアライズ機能が追加されました。この機能を使うことで、多くの場面でContentTypeWriter/ContentTypeReaderを書く必要がなくなりました。

自動XNBシリアライズ機能の使い方

自動XNBシリアライズは.Netのリフレクション機能を使って実装されています。ContentTypeWriter/ContentTypeReaderが指定されていない場合、自動XNAシリアライズが発生し、以下のルールに沿って動作します。

  • 公開(public)メンバー、プロパティがシリアライズの対象になる
  • シリアライズしたくないメンバーがある場合は、[ContentSerializerIgnore]属性を指定することで、シリアライズしないように指定できる
  • private, protected, internalなどの非公開メンバーをシリアライズしたい場合は[ContentSerializer]属性を指定する
  • シリアライズしたいデータ構造が再帰的な参照を含む場合は[ContentSerializer(SharedResource = true)]を指定する
  • コンテント・パイプライン内とランタイム時の型が違う場合、[ContentSerializerRuntimeType(“タイプ名”)]を指定する

シンプルな例

例えば以下のようなデータをゲームで扱うとします。ゲームとコンテント・パイプラインで共有するデータなので、ゲームプロジェクトの他に共有するデータを入れるためのプロジェクトを作る必要があります。例えばMyDataTypesという名前のプロジェクトです。

// ねこクラス
public class Neko
{
    public string Name;     // 名前
    public float Weight;    // 体重
    public int Tails;       // 尻尾の数
}

このデータをコンテント・パイプライン内に読み込むために以下のようなXMLファイル、cats.xmlを書きます。

<?xml version="1.0" encoding="utf-8" ?>
<XnaContent>
  <Asset Type="MyDataTypes.CatData[]">
    <Item>
      <Name>たま</Name>
      <Weight>8</Weight>
      <Tails>1</Tails>
    </Item>
    <Item>
      <Name>ねこまた</Name>
      <Weight>12</Weight>
      <Tails>2</Tails>
    </Item>
  </Asset>
</XnaContent>

作ったXMLファイルをコンテントとして追加した後に、そのデータを読み込むコードをゲーム側のコード(LoadContentメソッド内など)に以下のコードを追加します。

Neko[] cats = Content.Load<Neko>("cats");

後は、F5を押すだけで変数catsには必要なデータが読み込まれます。3.0以前のようにContentTypeReader/ContentTypeReaderを書かなくても良いので、データ構造の宣言、データファイルの生成、読み込み部分のコードをゲーム内に追加という単純な作業でカスタムデータをゲーム内で読み込むことができます。

オフラインとランタイムで型が違う場合

XNA GS 3.1のコンテント・パイプライン内で使われる型の図を見ると、コンテント・パイプライン内ではTexture2DContentだけど、ゲーム内ではTexture2Dのように、オフライン時とランタイム時では型が異なるケースがあります。例えば、ゲーム内では以下のデータ構造にしたい場合があるとします。

// ねこクラス
public class Neko
{
    public string Name;         // 名前
    public Texture2D Texture;   // テクスチャ
}

この場合、Textureはコンテント・パイプライン内で使う型がTexture2DContentとなるので、以下のようにContentSerializerRuntimeType属性を使ってランタイム時の型を指定します。

// ねこコンテント
[ContentSerializerRuntimeType("MyGame.Neko, MyGame")]
public class NekoContent
{
    public string Name;         // 名前
    public Texture2DContent Texture;   // テクスチャ
}

パフォーマンス

以上のように自動XNBシリアライズ機能は非常に便利な機能ですが、前述のようにデータの書き込みと読み込み時にリフレクション機能を使用するのでContentTypeWriter/ContentTypeReaderに比べると不必要なメモリ確保や、ボクシングなどが発生し、処理時間も多く掛かるので理論的にはロード時間が以前より長くなります。

ですが、現実的にはカスタムデータの多くがゲーム内のパラメーターなど、他のコンテントに比べると数は少ないものなので、ロード時間が長くなるといっても無視できる範囲のことが多いと思われます。

ただし、カスタムデータを数百、数千の配列で持つようなケースでは極端にロード時間が長くなる場合もあります。その場合は、今までどおりContentTypeWriter/ContentTypeReaderを使うことによってリフレクションによる速度低下を防ぐことができます。便利さを優先するには自動XNBシリアライズ、速度を優先するならContentTypeWriter/ContentTypeReaderといった感じに上手に使い分けましょう。

サンプル・コード

自動XNBシリアライズ機能を使ってコンテント・パイプラインの紹介の時に作った日本語表示のサンプルを3.1用に書き換えてみました。元のサンプルと比べると、共有するデータ型を記述するプロジェクト自体を書く必要がなくなり、ContentTypeWriter/ContentTypeReaderを書く必要が無くなったのでシンプルになっています。

http://higeneko.net/hinikeni/sample/TextMessageSample31.zip

元ネタ:http://blogs.msdn.com/shawnhar/archive/2009/03/25/automatic-xnb-serialization-in-xna-game-studio-3-1.aspx

AppWeek

XNAプラットフォーム&ツールチームの主な作業はXNA Game Studioを作ることですが、リリースの直前には、実際に作ったものに問題がないか、また使いやすいかを確認するために開発中のフレームワークを使って実際にゲームを作ってみるAppWeekというものがあります。

XNA GSE 1.0の頃に作られた物の中にはCatapultMinjiといったサンプルとして公開されているものもあります。XNA GS 3.0の時は忙しすぎてAppWeekをする暇がなかったのですが、今回のXNA GS 3.1ではAppWeekがありました。

今回のAppWeekは4日間という短期間でしたが15個のゲームが作られました。特に3.1で追加された機能のテストを兼ねているので今回はアバターとビデオを使ったゲームが殆どでした。

Shawn Hargreaves氏はCreators Club Onlineサイトにあるサンプルコードを組み合わせて作った「Super Avatar Sample Smashup EXTREME! ‘Capture the Cat’ edition」を作りました。このゲームではフィールド内の相手陣地のねこを自陣までつれてくるのが勝利の条件で、一匹のねこをめぐって宇宙船やら、戦車を使った争奪戦を繰り広げるネットワーク対応のゲームでした。

Brandon Bloom氏の作った「Avatar Boxing」は、アバターを使ったボクシングゲームでした。アバターの各ボーンを自由に変更することで任意のアニメーションを設定で機能を使ったものでした。ちなみにこのゲームを作るときにはBoundingSphere.Transformがサポートするのは平行移動のみの行列でしたが、それでは不便ということで3.1では平行移動、回転、スケールの行列もサポートするようになりました。

 

私も3日間だけ時間が取れたので、アバターを使ったゲームを作ってみました。タイトルは「Columns Flash」、落ちものパズルゲームにアバターを組み合わせたものです。今回は贅沢にすべてのブロックを3Dにしてみました。

ブロックを消すと、下のスクリーンショットのようにアバターが拍手するアニメーションしたり、

itoxe1-71

コンボゲージが溜まったときにYボタンを押すと、下のスクリーンショットのようにコンボアタックを発動して対戦相手に対していじわる攻撃を繰り出すことができます。

itoxe1-69 

こちらはLv1コンボアタックの「ゆさぶり」で、相手のボード全体がふらふらと揺れてプレイししづらくなります。もちろん、アバターも困った顔をしています。3Dで描画しているので、コンボアタックも3Dならではの攻撃になっています。

itoxe1-70

 

こちらは、Lv2コンボアタックの「ちぢこまり」です。ボードが小さくなるだけなのですが、この攻撃をされた対戦相手は画面が良く見えなくなるので、モニタの前に張り付く姿が笑えます。

 itoxe1-73

で、こちらはLv3コンボアタックの「ひっくりがえし」です。ボードが180度回転して、ブロックの上下が逆さまになります。この手のゲームでは連鎖のタネをばら撒くのが勝つコツですが、そういった計画を文字通りひっくりがえしてしまう攻撃です。アバターも驚いた表情をします。

 itoxe1-76

対戦が白熱してくると、やられたらやり返すという場面が増えてくるので、二人同時にコンボアタック発動して下のように派手な画面になることもしばしばあります。

itoxe1-74

そして、ゲームオーバーになると、下の画面のように負けたほうのブロックが飛び散り、勝ったプレイヤーのアバターは喜びのアニメーション、負けた方はうなだれたアニメーションをするようになっています。

itoxe1-75

 

このゲームではアバターの基本描画と、基本アニメーションセットしか使っていませんが、それでも自分のアバターがそのままゲーム内でさまざまなリアクションをするというのは楽しいものでした。また、今回のゲームは絵も含めてフルスクラッチ(背景の写真はハイキングへ行ったときに撮った写真)で制作しましたが、3日間という短期間でここまでのゲームを作ることができるという、C#を使った開発効率の高さにはいまだに感心させられます。

XNA GS 3.1のコンテント・パイプラインで使われる型

以前、Shawn Hargreaves氏のブログでXNA GS 2.0のコンテント・パイプライン内でデータが変換される過程図が紹介されました。

http://blogs.msdn.com/shawnhar/archive/2007/10/10/content-pipeline-types.aspx

XNA GS 3.1がリリースされたので、それに合わせて元の図を翻訳し、SoundEffect、Video等の新しい型を追加した図を作ってみました。アセットファイルがどのようにインポートされ、プロセッサで処理され、ContentTypeWriterからXNBファイルとして書き出されるまに使われる型情報を網羅しているので、カスタムインポータや、カスタムプロセッサを作るときに役立つと思います。

左側の水色の部分はコンテント・パイプライン内で処理される部分で、右側の黄色い部分はゲーム実行時に処理される部分です。

ContentPipelineTypes

動画再生

かんたん動画再生機能

XNA GS 3.1で動画を再生するには以下のステップを踏みます。

  • 再生したい動画をコンテントとして追加する
  • Video情報をContent.Load<Video>で読み込む
  • VideoPlayerクラスを使って再生
  • VideoPlayer.GetTexture()メソッドを使って現在のフレームのテクスチャを取得し描画する

変数宣言

動画再生するには、Videoクラスと、VideoPlayerクラスを使います。Videoクラスには再生する動画ファイル情報、動画のサイズやフレームレートなどの情報を含んでいます。VidoPlayerクラスは実際に動画再生するためのクラスで、このクラスを介して動画の再生、停止、一時停止、ループ再生の設定、現フレームをテクスチャとして取得することができます。

// 再生する動画
Video video;

// 動画再生用のプレイヤーインスタンス
VideoPlayer videoPlayer = new VideoPlayer();

動画情報の読み込みと再生

動画情報の読み込みは他のアセットと同様、コンテントマネージャーを介して行います。読み込んだ動画情報をビデオプレーヤーに渡すことで動画再生をすることができます。

Xbox 360の上で、デコーディングなどの動画再生処理は別スレッドで行われているのでゲームのメインスレッドへの影響は最小限になっています。

// 動画情報の読み込み
video = Content.Load<Video>("bear");

// ループ再生の設定
videoPlayer.IsLooped = true;

// 指定のビデオを再生する
videoPlayer.Play(video);

再生フレームの取得

動画の音声部分は自動的に再生されますが、画像部分を表示するにはビデオプレイヤーから現在のフレームをテクスチャとして取得し、SpriteBatch等を使って描画します。GetTextureを呼ぶタイミングは動画のfpsに関係なくいつでも取得することができます。

XNA GS 3.1の動画再生では常に実時間に合わせて動画再生を行うように実装されています。例えば高解像度の動画を性能の低いPC上で再生した場合は動画のフレームレートは落ちるけど、時間的には正しい動画再生をするようになっています。

取得するテクスチャは別スレッドでデコードされた時間的に最も近い動画フレームです。ですから、30fpsの動画を60fpsで動作しているゲーム内で取得した場合は、2フレームが同じ動画フレームを取得することになり、逆に60fpsの動画があった場合、30fpsで動作しているゲーム内で動画フレームを取得した場合は動画データのフレームがスキップすることになります。

// 現在の再生フレームをテクスチャとして取得
Texture2D texture = videoPlayer.GetTexture();

// 取得したテクスチャを使って描画
spriteBatch.Begin();
spriteBatch.Draw( texture, pos, Color.White);
spriteBatch.End();

以下は、Windowsについてくるbear動画をXNA GS 3.1上で再生したものです。10行以下のコードで簡単に動画を再生できるようになっています。

itoxe1-59

また、再生フレームはテクスチャとして使えるので、取得したテクスチャを3Dモデルに適応することもできます。下の図を見て複数の動画が再生していると気づいた人もいると思いますが、VideoPlayerのインスタンスは複数作ることができ、それぞれに別々の動画を再生することもできます。ただし、それぞれの動画をデコード処理するのに時間が掛かるので、複数のHD動画再生するにはかなりのCPUパワーを必要とすることに注意してください。

itoxe1-60

 再生フォーマット

XNA GS 3.1で再生できる動画の条件として以下があります。

  1. DRM(デジタル著作権管理)が施された動画は再生できない
  2. WMV-9 “Main”プロファイル、VC-1エンコードされたもの
  3. CBR(固定ビットレート)
  4. 音声トラックがあること
  5. 音声トラックはWMAエンコード、1パスCBR
  6. XNA GSがサポートする最大ビットレートは以下の通り
プロファイル レベル 最大ビットレート 解像度とフレームレート
Main Low 2 Mbps 320 x 240 @ 24Hz (QVGA)
  Medium 10 Mbps 720 x 480 @ 30Hz (480p)
720 x 576 @ 25Hz (576p)
  High 20 Mbps 1280 x 720 @ 30Hz (720p)

XNA Game Studio 3.1がリリース

XNA Game Studio 3.1がリリースされました。

http://www.microsoft.com/downloads/details.aspx?FamilyID=80782277-d584-42d2-8024-893fcd9d3e82&displaylang=en

インストール時の注意点

インストールするときの注意として、3.0をアンインストールしてから3.1をインストール必要があります。XNA GS 3.1では3.0、3.1の両方をサポートしているので、3.0で作ったゲームがそのまま遊べますし、3.0で作ったプロジェクトもそのまま読み込むことができます。また、プロジェクトメニューから3.1用のプロジェクトへの更新もできるようになっています。

新機能

XNA GS 3.1では以下の新機能が追加されました。

  • アバター(Xbox 360のみ)
  • 動画再生
  • XNBファイルの自動シリアライズ
  • Xbox Live Party (Xbox 360のみ)

アバターAPIではゲーマープロフィールのアバター情報から、アバターを描画することができ、いくつかの基本的なアニメーションを再生することができます。また、任意のボーンデータを設定することで自分のアバターをゲーム内でキャラクターとして使用することもできます。

動画再生ではWMVファイルをコンテントとして追加することで、ゲーム内で動画再生することが可能となりました。2Dの動画再生にも使えますが、動画フレームはテクスチャデータとして取得できるので3Dモデルに貼り付けて再生することもできます。

今まではXNBファイルに独自のデータ形式を格納するにはそれぞれの型に対応するTypeWriter/TypeReaderを作る必要がありましたが、XNBファイル自動シリアライズ機能を使うことで、TypeWriter/TypeReaderを書かなくても独自のデータ形式を簡単に使えるようになりました。

Xbox Live Party機能、今までは複数の友人をプレイしているゲームに招待する場合に、ユーザーがひとりひとり手動で招待する必要がありましたが、LocalNetworkGamer.SendPartyInvitesメソッドを使うことでプログラム側から複数のパーティーメンバーを一気に招待することができるようになりました。ユーザーがパーティーメンバーであるかどうかを調べるにはSignedInGamer.PartySizeプロパティの内容で判断できます。

次回からは、これらの新機能の詳しく紹介していきます。

Posted by Yuichi Ito | 0 Comments
Filed under: , ,

vFetchでスキンアニメーション

2009/06/25 追記: XNA GS 3.1用のサンプルを http://higeneko.net/hinikeni/sample/xna31/vFetchSkinningSample.zipにアップしました。

vFetchでスキンアニメーション、その3:vFetchでスキンアニメーション

スキンアニメーションで使えるボーン数を増やそうシリーズの記事も10回目となる今回で終わりです。今回はvFetchを使ったスキンアニメーションの実装例を紹介します。

XNA Game Studio 3.0で動作するサンプルを用意しました。基本的にSkinned Modelサンプルと同じ使い方ですが、今回のサンプルはXbox 360上でのみ動作することに注意してください。

http://higeneko.net/hinikeni/sample/vFetchSkinningSample.zip

  また、今回のサンプルはクォータニオンでスキンアニメーションサンプルに以下の変更を加えたものになっています。

  • BoneVertexの実装
  • AnimationPlayerの変更
  • モデルの頂点宣言の変更
  • ボーン格納用頂点バッファの生成
  • シェーダーの変更

BoneVertexの実装

ボーン情報を頂点バッファへ格納するためのBoneVertex構造体を作ります。この構造体には実際のデータの他に頂点要素宣言と、ストライド情報が含まれています。

この宣言の仕方は独自の頂点データを宣言する基本的な手法です。StructLayoutアトリビュートを使って構造体のメンバーがコンパイラーによって並び替えが起こらないようにし、静的なVertexElements配列と、構造体のバイトサイズを返すSizeInBytesを宣言します。

/// <summary>
/// ボーン格納用の頂点構造体
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct BoneVertex
{
    /// <summary>
    /// 回転部分(クォータニオン) 
    /// </summary>
    public Quaternion Rotation;

    /// <summary>
    /// 平行移動部分
    /// </summary>
    public Vector3 Translation;

    public BoneVertex( Quaternion rotation, Vector3 translation )
    {
        Rotation = rotation;
        Translation = translation;
    }

    /// <summary>
    /// 頂点宣言
    /// </summary>
    public static VertexElement[] VertexElements = {
        // Rotation (16バイト)
        new VertexElement(1, 0, VertexElementFormat.Vector4,
                                VertexElementMethod.Default,
                                VertexElementUsage.TextureCoordinate, 1),
        // Traslation (12バイト)
        new VertexElement(1,16, VertexElementFormat.Vector3,
                                VertexElementMethod.Default,
                                VertexElementUsage.TextureCoordinate, 2),
    };

    /// <summary>
    /// ストライドの取得
    /// </summary>
    public static int SizeInBytes
    {
        get { return Marshal.SizeOf( typeof( BoneVertex ) ); }
    }
}

AnimationPlayerの変更

クォータニオンによるスキンアニメーションサンプルからの変更点としては、SkinRoataions、SkinTranslationsをSkinTransformsに変換、GetSkinTransformsメソッドはBoneVertex配列を返すように変更します。

モデルの頂点宣言の変更

ボーン情報を頂点ストリームに格納するので、頂点宣言を変更する必要があります。元となる頂点宣言に指定した頂点要素を追加するExtendVertexDeclarationメソッドを持つRenderHelperクラスを作ります。

ModelMeshPart.VertexDecralationは読み込み専用のプロパティなので、変更した頂点宣言はModelMeshPart.Tagプロパティに格納します。

// モデルのMeshPartのVertexDeclarationにvFetch用の頂点宣言を
// 追加してMeshPart.Tagに格納する
foreach ( ModelMesh mesh in currentModel.Meshes )
{
    foreach ( ModelMeshPart meshPart in mesh.MeshParts )
    {
        meshPart.Tag = RenderHelper.ExtendVertexDeclaration(
                meshPart.VertexDeclaration, BoneVertex.VertexElements );
    }
}

ボーン格納用頂点バッファの生成

続いてボーン情報を格納するための頂点バッファを生成します。これも「頂点テクスチャでスキンアニメーション」の時と同じように、連続してデータを書き込むためのWritableVertexBufferというクラスを作って使用します。

// ボーン情報を書き込むための頂点バッファの生成
boneVB = new WritableVertexBuffer( GraphicsDevice,
        animationPlayer.GetSkinTransforms().Length * BoneVertex.SizeInBytes, 2 );

こうして生成した頂点バッファに以下のようにしてボーン情報を書き込み、ストリーム1として設定します。

// ボーンの情報を頂点ストリームに書き込む
boneVB.Flip();
int offset = boneVB.SetData<BoneVertex>( animationPlayer.GetSkinTransforms() );

gd.Vertices[1].SetSource( boneVB.VertexBuffer, offset, BoneVertex.SizeInBytes );

実際の描画ですが、ModelMeshPart.Tagに格納した頂点データを使うので、単純にModelMesh.Drawメソッドを呼ばずに、個々のModelMeshPartを描画する必要があります。

foreach ( ModelMesh mesh in currentModel.Meshes )
{
    foreach ( Effect effect in mesh.Effects )
    {
        effect.Parameters["World"].SetValue( world );
        effect.Parameters["View"].SetValue( view );
        effect.Parameters["Projection"].SetValue( projection );
    }

    // 
    gd.Indices = mesh.IndexBuffer;
    foreach ( ModelMeshPart meshPart in mesh.MeshParts )
    {
        // ボーン情報の頂点要素を追加した頂点宣言を使う
        gd.VertexDeclaration = meshPart.Tag as VertexDeclaration;

        gd.Vertices[0].SetSource( mesh.VertexBuffer,
                            meshPart.StreamOffset, meshPart.VertexStride );

        Effect effect = meshPart.Effect;
        effect.Begin();
        effect.CurrentTechnique.Passes[0].Begin();

        gd.DrawIndexedPrimitives( PrimitiveType.TriangleList, meshPart.BaseVertex,
                                0, meshPart.NumVertices, meshPart.StartIndex,
                                meshPart.PrimitiveCount );

        effect.CurrentTechnique.Passes[0].End();
        effect.End();
    }
}

シェーダーの変更

シェーダーの変更点は、vfetch命令を使って自前で頂点データをフェッチし、boneIndicesを使ってボーン情報をフェッチするだけです。後はクォータニオンを使ったスキンアニメーションと同じ処理をします。

//-----------------------------------------------------------------------------
// 頂点シェーダー
//=============================================================================
VS_OUTPUT VertexShader(int index : INDEX)
{
    float4 position;
    float4 normal;
    float4 texCoord;
    float4 boneIndices;
    float4 boneWeights;

    // vfetchはアセンブリ命令なのでasmブロックを使う必要がある
    asm
    {
        // 頂点データのフェッチ
        vfetch position,        index, position0
        vfetch normal,            index, normal0
        vfetch texCoord,        index, texcoord0
        vfetch boneIndices,        index, blendindices
        vfetch boneWeights,        index, blendweight
    };
    
    // ボーン情報のフェッチ
    float4 q1, q2, q3, q4;
    float4 t1, t2, t3, t4;
    
    asm
    {
        vfetch q1,    boneIndices.x, texcoord1
        vfetch q2,    boneIndices.y, texcoord1
        vfetch q3,    boneIndices.z, texcoord1
        vfetch q4,    boneIndices.w, texcoord1
        
        vfetch t1,    boneIndices.x, texcoord2
        vfetch t2,    boneIndices.y, texcoord2
        vfetch t3,    boneIndices.z, texcoord2
        vfetch t4,    boneIndices.w, texcoord2
    };

vfetchを使った開発

vfetch命令はXbox 360上でしか使うことができないので、いきなりシェーダー全体をvfetchを使って書いてしまうと、うまく動作しなかったときにvfetchの仕方が悪いのか、シェーダー内の処理自体にバグがあるのかを特定するのが難しくなります。

ですから、vfetchを使ったシェーダーコードを書く場合、実際にvfetch命令を使ったシェーダーを書く前にWindows上で動作するシェーダーを書き、シェーダーが問題無く動作するのを確認してから、vfetchを使ったものに書き換えるようにすると良いでしょう。ここで問題が起きた場合はvfetch部分が原因だと特定できるので、時間の節約になります。

vFetchの使い方

vFetchでスキンアニメーション、その2:vFetchの使い方

Fetchを使ったスキンアニメーションの実装例を紹介する前にvfetchの基本的な使い方を紹介します。

例えば、以下のシェーダーコードのように頂点位置、色を使用する頂点シェーダーがあるとします。

VS_OUTPUT VertexShader(float4 position: POSITION, float4 color : COLOR )
{
}

上のコードと同じ動作をするコードはvFetchを使って以下のように書くことができます。

// INDEXセマンティクスを使ってインデックス値を取得する
VS_OUTPUT VertexShader(int index : INDEX)
{
    float4 pos;    // フェッチしたデータを格納するための変数、float4のみ指定できる
    float4 col;

    // vfetchはアセンブリ命令なのでasmブロックを使う必要がある
    asm
    {
        vfetch pos, index, position        // positionのフェッチ
         vfetch col, index, color        // colorのフェッチ
    };
    

最初にインデックスバッファから読み込んだインデックス値を取得するためにINDEXセマンティクスを使います。次にフェッチしたデータを格納する為の変数を宣言します。この変数はfloat4型でないといけません。それ以外の型を指定するとコンパイルエラーになります。

次にvfetch命令を使って頂点データをフェッチします。vfetchはアセンブリ命令なのでasmブロック内に書く必要があります。vfetchの書式は以下の様になっています。今までは読みやすいようにvFetchと書いてきましたが、実際の命令は全て小文字のvfetchになります。

vfetch 取得したデータを格納する変数名, 頂点インデックス, セマンティクス

ここではインデックスバッファから取得した頂点インデックスを使用していますが、任意のインデックスを指定することができます。このことを利用して、GameFest Japan 2009のデモでは、index内に元の頂点インデックスとインスタンスのインデックスの2つの情報を算術符号化圧縮を使って格納し、シェーダー内で展開、頂点データとインスタンス用のデータをvFetchを使って取得しています。

vfetchに指定するセマンティクスはシェーダーで指定する入力セマンティクスの小文字となっています。大文字が混ざっているとコンパイルエラーになります。また、texcoord0, texcoord1のようにセマンティクスの後に数字を指定することもできます。下の表は入力セマンティクスとvFetchで指定するセマンティクスの対応表です。

入力セマンティクス

vFetchセマンティクス

説明

BINORMAL binormal バイノーマル(従法線)
BLENDINDICES blendindices ブレンドインデックス
BLENDWIEHGTS blendweight ブレンドの重み
COLOR color カラー
NORMAL normal 法線
POSITION position 頂点位置
PSIZE psize ポイントサイズ
TANGENT tangent タンジェント(接線)
TEXCOORD texcoord テクスチャ座標

まとめると

  • 頂点インデックスはINDEXセマンティクスを使って取得する
  • フェッチしたデータを格納する変数の型はfloat4にする
  • vfetchはasmブロック内に記述する
  • vfetchのセマンティクスは入力セマンティクスの小文字になっている

と、なります。

次回は、このvFetchを使ったスキンアニメーションの実装例を紹介します。

vFetchってなに?

vFetchでスキンアニメーション、その1:vFetchってなに?

Xbox 360のGPUはDirect X 9.0とDirect X 10の中間であると言われることがありますが、vFetchはその特徴を示すひとつの機能です。vFetchはシェーダー内で使えるアセンブリ命令で、Vertex Fetchの略、つまり頂点データをフェッチをするための命令です。

vFetchを使うことで頂点数の増減こそできませんが、Direct X 10で追加されたジオメトリシェーダーを使ったファーシェーダーのように隣接する頂点データをフェッチしてフィンポリゴンをリアルタイムに生成したりすることもできます。

自由に頂点をフェッチできるというのが最大の利点ですが、弱点もあります。

  • Windows上では使えない
  • アセンブリ命令なので、頂点フェッチの最適化などを自前でする必要がある

vFetchはWindows上では使うことができないので、Windows/Xbox 360両対応のゲームを製作する場合にはそれぞれのプラットフォーム別に違う手法を実装する必要があります。

vFetch命令はメモリアクセス命令でもあるので、使用する場所を間違えると大きなパフォーマンスロスになってしまいます。通常はシェーダーコンパイラーが最適化してくれるのですが、明示的にvFetchを使うときには注意が必要です。vFetchを最適な状態で使用していないと40%程のパフォーマンスロスになってしまった、という経験が私自身ありました。

GPUはどうやって頂点データを読んでいるのか?

vFetchの使い方を知るには、GPUがどのように頂点データを読み込んでいるのかを知っておくと理解しやすいと思います。

頂点シェーダー内で頂点データを読み込むには以下の三つの情報が不可欠です。

  • シェーダー内の入力セマンティクス
  • 頂点宣言(VertexDeclaration)
  • 頂点ストリーム

シェーダー内の入力セマンティクス指定は以下のコードように「変数名 : セマンティクス名」となります。この宣言によって、以下の例では、position変数にはPOSITIONセマンティクス、normal変数にはNORMALセマンティクスをといった感じに、任意の変数に指定したセマンティクスを割り当てています。

VS_OUTPUT VertexShader(float4 position : POSITION, float3 normal : NORMAL)
{
    // ...

次に、頂点宣言では複数の頂点要素(VertexElement)を指定することで、セマンティクスとメモリレイアウトとの関連付けをします。セマンティクスはVertexElementUsageで表されます。頂点宣言によって、任意のセマンティクスがどのストリームのどのメモリアドレスからどんなフォーマットで読み込むのかを指定します。

下の頂点要素の宣言は「POSITIONはストリーム番号0番、頂点データの12バイト目からVector3フォーマットで格納されている」という意味になります。

new VertexElement(0, 12, VertexElementFormat.Vector3,
                        VertexElementMethod.Default,
                        VertexElementUsage.Position, 0),

そして、最後に頂点ストリームの設定です。頂点ストリームの設定はVertexStream.SetSourceメソッドで行います。以下のコードの意味は「ストリーム0番はメッシュの頂点バッファの100バイト目から始まり、ストライドサイズは64バイト」という意味です。通常、ストライドサイズはひとつの頂点データと同じサイズですが、別のサイズでも問題ありません。例えば頂点サイズは20バイトだけど、他にも頂点データ以外のデータが入っているのでストライドサイズは48バイトなんていう指定の仕方もできます。

GraphicsDevice.Vertices[0].SetSource( mesh.VertexBuffer, 100, 64 );

ストリームと頂点宣言の関係をまとめると以下の図のようになります。

stream

頂点バッファには頂点という名前がついているので、GPUが読み込める頂点データ形式だけを格納するべきものだと思われがちですが、GPUからすると頂点バッファは単なるメモリの塊に過ぎないので上図のように、GPUで読み込むべきPosition, Normal, Color以外にGPUが呼ぶ必要がないSpeedがあっても全く問題ありません。GPUが気にするのはストリーム情報と頂点宣言だけです。

GameFest Japan 2008のデモでは、このことを利用してゲームオブジェクトのリストをそのまんま頂点バッファに設定して使用することでCPU側の無駄なコピー処理を省くDirect Mapping(私が勝手に命名)は、この仕組みを利用しています。

ただし、ストライドの最大サイズは256バイトなので、大きなデータ構造を指定できないことに注意してください。

GPUはシェーダー内のセマンティクス、頂点宣言、そしてストリームの情報を元に以下のように頂点データを読み込んでいます。ここではDrawIndexedPrimitiveを使い、頂点シェーダー内でposition変数の値を取得するまでの過程を説明します。

  1. インデックスバッファから頂点インデックスを取得
  2. POSITIONセマンティクスに該当する頂点要素の情報を見つける
  3. 頂点アドレスの計算
    • 頂点アドレス = 頂点バッファのアドレス + ストリームオフセット + ストライド × 頂点インデックス
  4. 頂点要素のメモリアドレスの計算
    • 頂点要素アドレス = 頂点アドレス + 頂点要素のオフセット
  5. 頂点要素のメモリアドレスから指定されたフォーマット形式でデータをposition変数に読み込む

以上のステップは普段はGPUで自動的に行われているのですが、この過程の1と2の部分をシェーダー内でやってしまおうというのがvFetch命令です。

次回に続く

頂点テクスチャでスキンアニメーション

2009/06/25 追記: XNA GS 3.1用のサンプルを http://higeneko.net/hinikeni/sample/xna31/TexSkinningSample.zipにアップしました。

頂点テクスチャでスキンアニメーション、その2:頂点テクスチャでスキンアニメーション

今回は頂点テクスチャを使ったスキンアニメーションの実装方法を紹介します。

XNA Game Studio 3.0で動作するサンプルを用意しました。基本的にSkinned Modelサンプルと同じ使い方です。

http://higeneko.net/hinikeni/sample/TexSkinningSample.zip

今回のサンプルは前々回の「クォータニオンでスキンアニメーション」のサンプルプログラムに以下の変更を加えたものです。

  • AnimationPlayerの変更
  • 頂点テクスチャの生成
  • シェーダーの変更

頂点テクスチャフォーマットを決める

まずは、頂点テクスチャにどのようにボーンデータを格納するか決めます。ボーンの回転部分のクォータニオンはVector4と同じフォーマットなのでSurfaceFormat.Vector4が使えます。平行移動にはVector3を使っていますが、頂点テクスチャのフォーマットにはVector3が無いのでちょっともったいないですがSurfaceFormat.Vector4を使用します。

今回のサンプルでは、回転部分と平行移動部分を2つの頂点テクスチャに別々に格納しています。ひとつのテクセルにひとつのボーン情報を格納しているので、頂点テクスチャのサイズは横がボーン数、縦が1となっています。

tex-bone

頂点テクスチャのサンプラーは4つしかないので、2つの頂点テクスチャを使うのが厳しい場合は以下のように、2つのテクセルにひとつのボーン情報をまとめて格納するといいでしょう。この場合、頂点テクスチャのサイズは横がボーン数×2、縦が1となります。

tex-bone-02

頂点テクスチャが使うメモリサイズを節約するという観点では前者の回転部分と平行移動部分を別々の頂点テクスチャで持つほうが有利です。これはクォータニオンの4要素の値の範囲は-1~+1なので、SurfaceFormat.Vector4の代わりにSurfaceFormat.HalfVector4やSurfaceFormat.NormalizedShort4を使うことでメモリ使用量を半分にすることができるからです。また、通常のキャラクターアニメーションでは平行移動部分も大きな値を使用しないのでSurfaceFormat.HalfVector4を使って更にメモリ使用量を減らすこともできるでしょう。

AnimationPlayerの変更

機能的には前々回のサンプルとまったく同じなのですが、頂点テクスチャへの格納フォーマットがVector4に変わったので、それに合わせてSkinTranslationsの型もVector3[]からVector4[]に変更します。

頂点テクスチャの生成

まずは、使用する頂点テクスチャ(ボーン用の頂点テクスチャなので、ボーンテクスチャと呼びます)の宣言をします。フレーム毎にボーン情報をボーンテクスチャへ書き込むわけですが、「GPUはいつ描画するのか?」で解説したように、同じテクスチャに続けて書き込むとGPUの処理とバッティングしてしまうということに注意が必要です。

そこで、それぞれ複数の頂点テクスチャを生成して切り替えながら使う必要があります。この実装は非常に単純でTexture2Dの配列と、現在使用するテクスチャのインデックスを用意するだけでいいのですが、使用する頂点テクスチャが増えてくると余計な変数が増えてきて、コードの可読性が低くなり、ミスも起きやすくなってしまいます。そういった理由から、ここでは複数のテクスチャを切り替える機能をもったFlipTexture2Dというクラスを作ります。

FlipTexture2DクラスはTexture2Dと同様のコンストラクタを持ち、内部で複数のテクスチャを作り、Flipメソッドで使うテクスチャを切り替え、Textureプロパティで現在のテクスチャを返すようになっています。

    // ボーン情報を格納するテクスチャ
    FlipTexture2D rotationTexture;      // ボーンの回転部分を格納するテクスチャ
    FlipTexture2D translationTexture;   // ボーンの平行移動部分を格納するテクスチャ

ボーンテクスチャの生成は前述のように、横がボーン数、縦が1のサイズのテクスチャを作ります。ここでTextureUsageをTextureUsage.Linearを指定していることに注意してください。通常のテクスチャはピクセルシェーダー内でフェッチされることを前提としており、その用途に適しているTextureUsage.Tilingを使用するようになっています。TextureUsage.Noneを設定しても、テクスチャサイズがタイリングに適している場合は自動的にタイリングを使うようになっています。

通常のレンダリング時には極力タイリングを使うべきですが、SetDataを呼び出したときに通常のフォーマットからタイリングフォーマットへの変換がCPUによって行われます。

今回はボーンテクスチャとして使用するので、タイリングを使う必要がないこと、SetData時にタイリングフォーマット変換に掛かる時間を節約したいという二点の理由から、TextureUsage.Linearを指定します。

// 頂点テクスチャの生成
int width = animationPlayer.GetSkinRotations().Length;
int height = 1;

rotationTexture = new FlipTexture2D( GraphicsDevice, width, height, 1,
                        TextureUsage.Linear, SurfaceFormat.Vector4 );

translationTexture = new FlipTexture2D( GraphicsDevice, width, height, 1,
                        TextureUsage.Linear, SurfaceFormat.Vector4 );

こうして作ったボーンテクスチャにanimationPlayerで生成したSkinRotationsとSkinTranslationsをSetData<T>を使って書き込みます。ここではFlipTexture2D.Flipメソッドを呼び出すことで書き込むテクスチャを切り替えてから書き出します。

// ボーンのクォータニオンと平行移動部分を取得し頂点テクスチャに書き込み
rotationTexture.Flip();
translationTexture.Flip();

rotationTexture.Texture.SetData<Quaternion>( animationPlayer.GetSkinRotations() );
translationTexture.Texture.SetData<Vector4>( animationPlayer.GetSkinTraslations() );

頂点シェーダー内で頂点テクスチャをフェッチする場合に指定するのはテクスチャ座標なので、ボーン番号からテクスチャ座標に変換するために必要なtextureSizeを設定します。

実際の描画コードは以下のようになっています、通常の描画コードにrotationTexture、traslationTexture、そしてtextureSizeを設定するコードが追加しただけです。

Vector2 textureSize = new Vector2( rotationTexture.Texture.Width,
                                    rotationTexture.Texture.Height );

foreach ( ModelMesh mesh in currentModel.Meshes )
{
    foreach ( Effect effect in mesh.Effects )
    {
        effect.Parameters["BoneRotationTexture"].SetValue(
                                            rotationTexture.Texture );
        effect.Parameters["BoneTranslationTexture"].SetValue(
                                            translationTexture.Texture );

        effect.Parameters["BoneTextureSize"].SetValue( textureSize );
        effect.Parameters["World"].SetValue( world );
        effect.Parameters["View"].SetValue( view );
        effect.Parameters["Projection"].SetValue( projection );
    }

    mesh.Draw();
}

シェーダーの変更

シェーダー内での頂点テクスチャの宣言は通常のテクスチャと殆ど変わりありませんが、殆どのGPUでは浮動小数点テクスチャのバイリニアフィルタリングが利かないのと、正しいフィルター設定しないとフェッチができないものが多いので、ここではポイントサンプリング、ミップマップなし、テクスチャのアドレッシングをクランプに設定しています。

また、このコードではregister(vs, s0)というレジスタ宣言をして明示的に頂点テクスチャを任意のサンプラーに割り当てています。この宣言は必ずしも必要はありませんが、複数のエフェクトで同じ定数を共有したい場合にsharedを指定すると、コンパイラはサンプラーが通常のテクスチャなのか頂点テクスチャなのかを判断することができなってしまうので、registerを使って指定する必要があります。

//-----------------------------------------------------------------------------
// 頂点テクスチャ用の定数レジスタ宣言
//=============================================================================
float2 BoneTextureSize;    // ボーン用頂点テクスチャのサイズ

// ボーン用頂点テクスチャサンプラー宣言
texture BoneRotationTexture;

sampler BoneRotationSampler : register(vs,s0) = sampler_state
{
    Texture = (BoneRotationTexture);
    // 殆どのGPUでは以下のようなステート設定にしないと
    // 頂点テクスチャのフェッチがうまくいかない
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = Clamp;
    AddressV = Clamp;
};

texture BoneTranslationTexture;

sampler BoneTranslationSampler : register(vs,s1) = sampler_state
{
    Texture = (BoneTranslationTexture);
    // 殆どのGPUでは以下のようなステート設定にしないと
    // 頂点テクスチャのフェッチがうまくいかない
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = Clamp;
    AddressV = Clamp;
};

続いてボーン情報をボーンテクスチャからフェッチします。頂点テクスチャのフェッチにはtex2Dlodを使います。tex2Dlodにはfloat4値を渡し、x,y,zにはテクスチャ座標のu,v,wを、wにはミップマップレベルを指定します。ピクセルシェーダー内でおなじみのtex2Dが頂点シェーダー内で使えない理由はピクセルシェーダー内ではハードウェアが自動的にミップマップレベルを計算してくれますが、頂点シェーダー内ではミップマップレベルを自動的に計算することができないからです。ですから、tex2Dlodを使用してミップマップレベルを指定する必要があります。

4つのボーンインデックスから計算したテクスチャ座標を使ってそれぞれのボーン情報をフェッチして、前々回のサンプルで作ったCreateTransformFromQuaternionTransformsメソッドを使ってskinTransformを計算します。

//-----------------------------------------------------------------------------
// 頂点テクスチャからボーン情報のフェッチ
//=============================================================================
float4x4 CreateTransformFromBoneTexture( float4 boneIndices, float4 boneWeights )
{
    float2 uv = 1.0f / BoneTextureSize;
    uv.y *= 0.5f;
    float4 texCoord0 = float4( ( 0.5f + boneIndices.x ) * uv.x, uv.y, 0, 1 );
    float4 texCoord1 = float4( ( 0.5f + boneIndices.y ) * uv.x, uv.y, 0, 1 );
    float4 texCoord2 = float4( ( 0.5f + boneIndices.z ) * uv.x, uv.y, 0, 1 );
    float4 texCoord3 = float4( ( 0.5f + boneIndices.w ) * uv.x, uv.y, 0, 1 );

    // 回転部分のフェッチ
    float4 q1 = tex2Dlod( BoneRotationSampler, texCoord0 );
    float4 q2 = tex2Dlod( BoneRotationSampler, texCoord1 );
    float4 q3 = tex2Dlod( BoneRotationSampler, texCoord2 );
    float4 q4 = tex2Dlod( BoneRotationSampler, texCoord3 );

    // 平行移動部分のフェッチ
    float4 t1 = tex2Dlod( BoneTranslationSampler, texCoord0 );
    float4 t2 = tex2Dlod( BoneTranslationSampler, texCoord1 );
    float4 t3 = tex2Dlod( BoneTranslationSampler, texCoord2 );
    float4 t4 = tex2Dlod( BoneTranslationSampler, texCoord3 );
    
    return CreateTransformFromQuaternionTransforms(
                    q1, t1,
                    q2, t2,
                    q3, t3,
                    q4, t4,
                    boneWeights );
}

頂点シェーダー本体のコードは以下のようになります。前々回のサンプルではCreateTransformFromQuaternionTransformsを呼んでいた部分がCreateTransformFromBoneTextureに変わっただけです。

//-----------------------------------------------------------------------------
// 頂点シェーダー
//=============================================================================
VS_OUTPUT VertexShader(VS_INPUT input)
{
    VS_OUTPUT output;
    
    // スキン変換行列の取得
    float4x4 skinTransform =
                CreateTransformFromBoneTexture( input.BoneIndices, input.BoneWeights );
            
    skinTransform = mul( skinTransform, World );
  
    // 頂点変換
    float4 position = mul(input.Position, skinTransform);
    output.Position = mul(mul(position, View), Projection);

    // 法線変換
    float3 normal = normalize( mul( input.Normal, skinTransform));
    
    float3 light1 = max(dot(normal, Light1Direction), 0) * Light1Color;
    float3 light2 = max(dot(normal, Light2Direction), 0) * Light2Color;

    output.Lighting = light1 + light2 + AmbientColor;

    output.TexCoord = input.TexCoord;
    
    return output;
}

定数レジスタがいらなくなった

ボーン情報を頂点テクスチャから読み込んでいるので、定数レジスタの数制限からくるボーン数制限がなくなりました。ただし、XNAフレームワークのコンテント・パイプラインで変換されるボーンインデックスは最大256個までとなっているので、最大ボーン数は256個になります。もちろん、コンテント・パイプラインを拡張して出力するボーンインデックスのフォーマットを変更すれば更に多くのボーンを使うこともできますが、256個以上のボーンが必要になるケースというのはあるのでしょうか?

これでオリジナルのサンプルでは59個から4倍以上のボーン数を使えるようになりました。定数レジスタを必要としないので、余った定数レジスタは他の用途に使うことができます。

PC上ではシェーダーモデル3.0以上のビデオカードが必要になりますが、Xbox 360上では問題なく動くし、この手法を活用できる場面(近日中の記事で紹介予定)も多いので、魅力的な手法ではないでしょうか?

今回の手法で使えるボーン数が上限にまで達しましたが、次回はXbox 360専用の機能であるvFetchを使った手法を紹介したいと思います。

頂点テクスチャってなに?

頂点テクスチャでスキンアニメーション、その1:頂点テクスチャってなに?

前回紹介したクォータニオンを使ったスキンアニメーションの実装方法はシェーダーモデル2.0では最も多くのボーン数を使うことができる手法です。この手法を使うことでオリジナルのスキンアニメーションサンプルでは59個だった最大ボーン数が、二倍近い117個になりました。

 

もっと多くのボーン数を使える手法はないのでしょうか?

 

残念ながら、シェーダーモデル2.0ではここが限界ですが、シェーダーモデル3.0から追加された機能を使うことで更に多くのボーンを使うことができます。

使う機能とは以下の二つです。

  • 頂点テクスチャ
  • 浮動小数点テクスチャ

頂点テクスチャは、その名のとおり頂点シェーダー内でテクスチャデータをフェッチ(読み込み)できる機能です。発表当時(2004年)のデモでは波のシミュレーションをピクセルシェーダーで行い、その結果を頂点テクスチャとして使うことで、立体的な波を表現していました。

XNA Field

また、XNA GSE 1.0のXNA Fieldデモではマルチスレッドでリアルタイムにフラクタル生成した結果を頂点テクスチャとして使用して地形モデルを表示していました。

実際のゲームでも、バーチャファイター5では水面と雪原、フォグなどの表現で頂点テクスチャは使われています。

頂点テクスチャの特徴としては

  • 頂点シェーダー内で最大4種類のテクスチャをフェッチできる
  • 任意のテクスチャ座標から複数回の読み込みができる
  • 浮動小数点テクスチャと組み合わせることでさまざまなデータを扱うことができる
  • 定数レジスタに比べて大量のデータを扱うことができる

が、あります。

普段、テクスチャというと色情報が入った画像を使いますが、0-255の範囲の数値として扱うことで色以外の情報を格納するのにも使えます。これに加えてシェーダーモデル3.0から採用された浮動小数点テクスチャを使うことで、色以外の情報を格納するのが更に容易になりました。

また、頂点テクスチャの読み込みはピクセルシェーダー内でのテクスチャの読み込みと同様に自由にテクスチャ座標を指定することができ、複数回読み込むことができます。

これらの特徴を利用して、画像情報以外のデータを入れたテクスチャから自由にデータを読み込むことで頂点テクスチャを定数レジスタの変わりとして使うことができます。定数レジスタ数が256だったのに対して、頂点テクスチャでは大量のデータを格納することができます。例えば2,048x2,048のテクスチャは定数レジスタ数に換算すると400万という膨大な数のデータを格納することができます。

このように頂点テクスチャは非常に魅力的な機能ですが、弱点もあります。

  • 対応しているGPUはまだ少ない、対応していても動作が遅いものが少なくない
  • 浮動小数点テクスチャではフィルタリングが使えないものが殆ど
  • テクスチャに格納できる要素数が1,2,4と限定されている

頂点テクスチャは最近のGPUでは当たり前のようについている機能ですが、まだ対応していないGPUの数も多く、ピクセルシェーダー内でテクスチャフェッチをするのに比べて速度的に劣る場合が多いです。

また、頂点テクスチャで使用されるテクスチャはその性質上、浮動小数点テクスチャを使うことが多いのですが、Direct X 9.0世代の殆どのGPUでは浮動小数点テクスチャに対してのバイリニアなどのフィルタリングができないものが殆どです。しかも、できないだけなら良いのですが、テクスチャフィルタを設定した状態でフェッチするとでたらめな数値を返してきたりするので、正しいフィルタリング設定をせずに頂点テクスチャを使ってしまい、自分のプログラムが正しく動作しているかどうかを時間を掛けて調べたあげく、見つけた原因が単純にフィルタリング設定のミスだったなんてことがあります。

頂点バッファで使われる代表的な型といえばVector3ですが、残念ながら頂点テクスチャではVector3は使えません。浮動小数点型で使えるのはSingle、Vector2、そしてVector4だけです。これはテクスチャのテクセルサイズには16ビット、32ビット、64ビット、そして128ビットの組み合わせしか指定できないのが原因です。

頂点テクスチャを使おう

以上のように、頂点テクスチャは魅力的な機能なのですが、使えるGPUの少なさから今まではおまけ的な機能として使われることが殆どでした。ですが、最近では使えるGPUの数も増えてきたので有効活用するケースも増えてきました。特にXbox 360やPS3はどちらも頂点テクスチャをサポートしていて、使っていると思われるタイトルも見かけるようになりました。

次回は、この頂点テクスチャを使ったスキンアニメーションの実装例を紹介します。

More Posts Next page »
 
Page view tracker