XNA Game Studio 4.0における頂点データ

以前のXNAでは、VertexBuffer(頂点バッファ)は弱い方定義がされたバイト列の入れ物でしかありませんでした。これとは別にVertexDeclaration(頂点宣言)オブジェクトによって、これらのバイト列をどのようなフォーマットとして扱うのかを指定していました。

XNA Game Studio 4.0ではVertexBuffer生成時にVertexDeclarationを指定することでVertexBufferとVertrexDeclaratoinを関連付けるようになりました。このことによってVertexBufferは強い型定義となり、頂点宣言を含む必要な情報を提供できるようになりました。

どうして?

以下は4.0以前での頂点バッファの使用例です。赤でハイライトされた部分が問題点です。

 // 生成
VertexDeclaration decl = new VertexDeclaration(device, VertexPositionColor.VertexElements);
int size = VertexPositionColor.SizeInBytes * vertices.Length;
VertexBuffer vb = new VertexBuffer(device, size, BufferUsage.None);

vb.SetData(...);

// 描画
device.VertexDeclaration = decl;
device.Vertices[0].SetSource( vb, 0, VertexPositionColor.SizeInBytes );

device.DrawIndexedPrimitives(...);

何回も扱う頂点フォーマットを指定しているので、間違いを起こす危険性があります。また、つねに正しいVertexDeclarationや頂点ストライドをVertices.SetSourceで指定しないと正しく動作しません。

この弱い型定義設計はランタイム実装を難しくします。なぜなら、VertexBuffer自体には頂点フォーマット情報がないので、フレームワークが異なるプラットフォームやハードウェア用に最適化することができません(少なくとも実際に描画命令が発行するまでの間、しかも描画時には既に正しい最適化をするには遅すぎる)

例えば、コンテントパイプラインの中で頂点バッファはXbox 360用にエンディアン変換をしますが、ContentTypeWriter<VertexBufferContent>自体にはこの変換を行うための十分な情報がありません。そのかわりに、VertexContent.CreateVertexBufferにターゲットプラットフォームを指定する必要がありました。

新しい対応プラットフォームを増やしたことにより、それぞれのプラットフォームに特化したデータを柔軟に生成する必要性が大きくなり、その為には扱うデータの情報が必要になってきます。強い型定義の頂点バッファはAPIをシンプルにし、エラーが少なくなり、しかもランタイム実装の柔軟性を上げてくれるという良いこと尽くめです。

VertexDeclarationの変更点

VertexElementやVertexDeclarationは4.0でもありますが、以下の変更が加えられています。

  • VertexDeclarationコンストラクタにGraphcisDeviceを指定する必要がなくなった
  • 頂点ストライドはVertexDeclarationで指定するようになった
  • VertexElement.Streamプロパティの削除(下記を参照)
  • VertexElementMethod列挙型の削除(どのハードウェアも使っていないから)

そして、重要な変更点として、

  • GraphicsDevice.VertexDeclarationプロパティが無くなった

これは単純にVertexBuffer自体にVertexDeclarationの情報があるので必要がなくなったからです。私自身、Game Studio 3.1から4.0へ変更するのはとても楽だということに気づきました。なぜならGraphicsDevice.VertexDeclarationを使っている行を削除するだけで動作するからです。

 

VertexBufferの変更点

VertexBufferのコンストラクタの引数が変わりました。

  • 頂点フォーマットをTypeまたはVertexDeclarationで指定できるようになった
  • バッファサイズはバイト数ではなく頂点数で指定するようになった

また、

  • VertexBuffer.VertexDeclarationプロパティの追加
  • GraphicsDevice.Verteices[n].SetSourceに変わりにGraphicsDevice.SetVertexBufferメソッドを使うようになった

4.0では前述のコードが

 // 生成
VertexBuffer vb = new VertexBuffer(device, typeof(VertexPositionColor),
                                    vertices.Length, BufferUsage.None);

vb.SetData(...);

// 描画
device.SetVertexBuffer(vb);
device.DrawIndexedPrimitives(...);

と、なります。頂点フォーマットを指定する場所が一箇所になり、コード量が少なくなり、潜在的なエラーの可能性も減っています。

この変更により、ひとつの頂点バッファの中に異なる頂点フォーマットをオフセットを利用して複数格納するということができなくなりました。この手法は頂点バッファの切り替えコストが高いとき(DirectX 7時代)は有効な手段で、複数のモデルをひとつの大きな頂点バッファに格納するなどして使われていました。しかし、頂点バッファの切り替えコストが低くなっている現在では最適化手法としての効果は薄くなりました。

IVertexTypeインターフェース

前述のコードサンプルの中でtypeof(VertexPositionColor)を指定していることに気づいたでしょうか?VertexBufferはどうやってタイプ情報からVertexDeclaration情報を得ているのでしょうか?

それは新しく追加されたインターフェースを介して行われます。

 public interface IVertexType
{
    VertexDeclaration VertexDeclaration { get; }
}

このインターフェースはあらかじめ用意されている頂点構造体の全てで実装されています。このインターフェースを実装しているタイプからはVertexDeclaration情報を得ることができます。

もし、頂点バッファ生成時にIVertexTypeが実装されていないタイプを指定した場合、例外が発生します。モデルデータを読み込む時などによくあるシチュエーションとして、.Netタイプでは指定できない頂点フォーマットを使いたい場合にバイト列として読み込みたい時があります。この場合、VertexDeclarationを指定するコンストラクタを使います。

カスタム頂点構造体

カスタム頂点構造体を作るときには、あらかじめある頂点構造体と同じように使えるようにIVertexTypeを実装すると良いでしょう。

 struct MyVertexThatHasNothingButPosition : IVertexType
{
    public Vector3 Position;

    public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
    (
        new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
    );

    VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
};

DrawUserPrimitivesの変更点

DrawUserPrimitives<T>とDrawUserIndexedPrimitives<T>は頂点バッファと違ってマネージ配列を使用します。VertexBuffer以外を指定した場合にどうやってVertexDeclarationを取得できるのでしょうか?

  • もしTで指定した型がIVertexTypeを実装している場合は、そこからVertexDeclarationを取得します
  • もしTで指定した型がIVertexTypeを実装していない場合は、VertexDeclarationを指定できるオーバーロードを使うことができます。

複数の頂点ストリーム

4.0以前は複数の頂点ストリームをVertexElement.Streamプロパティを使うことで、ひとつのVertexDeclarationに格納し、以下のように頂点ストリームを指定していました。

 device.Vertices[0].SetSource(vb1, 0, stride1);
device.Vertices[1].SetSource(vb2, 0, stride2);

4.0ではVertexBufferとVertexDeclarationは一対一の関係になっています。つまり、複数のVertexBufferがあれば、同じ数だけVertexDeclarationがあることになります。ですから、VertexElement.Streamプロパティは必要がなくなり、複数の頂点ストリームは以下のように指定します。

 device.SetVertexBuffers(vb1, vb2);

コンテント・パイプラインの変更点

新しい頂点バッファの変更にともなって、コンテント・パイプラインにもいくつかの変更がなされています。

  • VertexDeclarationContent型の追加
  • VertexBufferContentはVertexDeclarationContentと関連付けされている
  • VertexBufferContent.WriteとVertexContent.CreateVertexBufferにTargetPlatformを指定する必要がなくなった
  • VertexBufferとIndexBufferプロパティはModelMeshからModelMeshPartへ移動
  • ModelMeshPart内のVertxDeclarationとVertexStrideを削除

原文:

http://blogs.msdn.com/shawnhar/archive/2010/04/19/vertex-data-in-xna-game-studio-4-0.aspx