WisdomSoft - for your serial experiences.

カスタム頂点

グラフィックスデバイス(ハードウェア)にとって、描画時に転送される頂点配列は単純なバイト配列であり、頂点の構造を頂点宣言によって伝えなければなりません。従って、頂点の値は特定のプログラミング言語に依存する型ではなく、開発者が必要に応じてカスタムの頂点を作成できます。

頂点宣言と頂点要素

これまで、頂点の型には XNA Framework で用意されている構造体を利用してきましたが、頂点はユーザーが任意に変更できます。頂点に設定したパラメータは、頂点からプリミティブを描画する過程で GPU から利用できます。頂点にどのようなパラメータを持たせるかは自由ですが、それを処理するには独自のエフェクトを作成しなければなりません。エフェクトについては後述するので、この場では、VertexPositionColor 構造体と互換性のある独自の型を作成して BasicEffect で描画しましょう。

VertexPositionColor 構造体のように XNA Framework で用意されている頂点型は空間座標やテクスチャ座標などが型として構造化されていますが(強い型付け)、グラフィックスデバイスにとって頂点配列はバイト配列でしかなく、.NET Framework のようなプラットフォームに依存した型情報は持ちません。例えば、頂点の座標の実体は float 型を 3 つ並べただけであり、色は 32 ビットの整数だと解釈できます。Vector3 型や Color 型である必要すらありません。

public struct CustomVertexValue
{
    public float X, Y, Z;
    public byte R, G, B, A;
}

上記の CustomVertexValue 構造体は VertexPositionColor 構造体と互換性のある独自の頂点型です。float 型の X フィールド、Y フィールド、Z フィールドが空間座標を表し、R フィールド、G フィールド、B フィールド、A フィールドが色を表します。型は異なりますが、構造は VertexPositionColor 構造体と同じです。

ただし、この構造体の配列をグラフィックスデバイスに渡しても、そのままでは頂点として描画することはできません。これが頂点型であることを伝え、頂点がどのような構造になっているか記述する必要があります。構造体が頂点型であることを表すには Microsoft.Xna.Framework.Graphics.IVertexType インターフェイスを実装します。

Microsoft.Xna.Framework.Graphics.IVertexType インターフェイス
public interface IVertexType

このインターフェイスは、頂点の構造を定義する VertexDeclaration プロパティを宣言しています。IVertexType インターフェイスを実装する型は、このプロパティを用意する必要があります。

IVertexType インターフェイス VertexDeclaration プロパティ
public VertexDeclaration VertexDeclaration { get; }

この VertexDeclaration プロパティが、グラフィックスデバイスに対して頂点の構造に関する情報を提供します。

グラフィックスデバイスが頂点配列から個々の頂点を取り出して計算するには、頂点の構造を理解しなければなりません。プログラムは、グラフィックスデバイスに対して使用する頂点がどのような構造になっているのかを事前に伝えなければなりません。これを行うのが頂点宣言です。

独自の頂点型を作成するには、その頂点型の構造を表す頂点宣言を作成しなければなりません。頂点宣言は Microsoft.Xna.Framework.Graphics.VertexDeclaration クラスで表されます。

Microsoft.Xna.Framework.Graphics.VertexDeclaration クラス
public class VertexDeclaration : GraphicsResource

頂点宣言は、頂点に含まれる要素(空間座標、テクスチャ座標、色など)の構造を含みます。VertexDeclaration クラスのコンストラクタには、以下のように頂点の構造を記述する頂点要素の配列を指定します。

VertexDeclaration クラスのコンストラクタ
public VertexDeclaration (
         params VertexElement[] elements
)
public VertexDeclaration (
         int vertexStride,
         params VertexElement[] elements
)

vertexStride パラメータには頂点のサイズをバイト単位で指定します。省略した場合、頂点要素から自動的に頂点のサイズを設定します。この単一の頂点サイズのことを頂点ストライドと呼びます。elements パラメータに、頂点に含まれる個々の要素を表す頂点要素を任意の数だけ指定します。

vertexStride パラメータに指定した値、もしくは頂点要素から計算された頂点ストライドは VertexStride プロパティから取得できます。

VertexDeclaration クラス VertexStride プロパティ
public int VertexStride { get; }

VertexStride プロパティは、この頂点宣言によって表される頂点のサイズをバイト単位で返します。

頂点に含まれている要素は Microsoft.Xna.Framework.Graphics.VertexElement 構造体によって表される頂点要素と呼ばれる情報に基づきます。

Microsoft.Xna.Framework.Graphics.VertexElement 構造体
[SerializableAttribute]
public struct VertexElement

頂点宣言は、頂点に含まれる要素の数だけ、対応する頂点要素、すなわち VertexElement 構造体の値を保有しています。例えば XNA Framework に組み込まれている VertexPositionColor 構造体による頂点は、空間座標と色の 2 つの要素で構成されています。よって、空間座標と色を表す 2 つの頂点要素からなる頂点宣言が必要と言うことになります。

VertexElement 構造体のコンストラクタには、要素の情報を各パラメータから設定します。

VertexElement 構造体のコンストラクタ
public VertexElement (
         int offset,
         VertexElementFormat elementFormat,
         VertexElementUsage elementUsage,
         int usageIndex
)

offset パラメータには位置を計算するために必要となる頂点の先頭から要素が出現するまでのバイト単位のオフセットを指定します。elementFormat パラメータには要素の型、elementUsage パラメータには要素の使用方法(意味)を表す値を指定します。usageIndex パラメータには elementUsage パラメータに指定した使用方法に対応したインデックスを指定します。usageIndex パラメータに指定する値の意味は elementUsage に指定した値によって意味が変化します。

頂点要素の型は Microsoft.Xna.Framework.Graphics.VertexElementFormat 列挙型によって表されます。

Microsoft.Xna.Framework.Graphics.VertexElementFormat 列挙型
public enum VertexElementFormat

この列挙型には、頂点の型を表すメンバが定義されています。例えば 3 つの 32 ビット浮動小数点からなるベクトルを表す Vector3 メンバや 32 ビット整数からなる色を表す Color といったメンバが用意されています。

頂点要素の使用方法は Microsoft.Xna.Framework.Graphics.VertexElementUsage 列挙型によって表されます。グラフィックスデバイスは VertexElementFormat 列挙型の値と VertexElementUsage 列挙型の値から、要素のサイズと計算時の使用方法を解釈します。

Microsoft.Xna.Framework.Graphics.VertexElementUsage 列挙型
public enum VertexElementUsage

この列挙型には、頂点の種類を表すいくつかのメンバが定義されています。この場では、座標データであることを表す Position メンバと、色であることを表す Color メンバを使います。どちらも VertexElement 構造体のコンストラクタに渡す usageIndex パラメータには 0 を指定してください。

コード1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public struct CustomVertexValue : IVertexType
{
    public float X, Y, Z;
    public byte R, G, B, A;

    public static readonly VertexDeclaration  VertexDeclaration;
    static CustomVertexValue()
    {
        VertexElement[] vertexElements = new VertexElement[] {
            new VertexElement(
                0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0
            ),
            new VertexElement(
                sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0
            ),
        };

        VertexDeclaration = new VertexDeclaration(vertexElements);
    }

    public CustomVertexValue(float x, float y, float z, uint color)
    {
        X = x;
        Y = y;
        Z = z;
        R = (byte)(color >> 24);
        G = (byte)(color >> 16);
        B = (byte)(color >> 8);
        A = (byte)color;
    }

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

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private BasicEffect effect;
    private CustomVertexValue[] vertices;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Initialize()
    {
        vertices = new CustomVertexValue[] {
	        new CustomVertexValue(0, 0.5F, 0, 0xFF0000FF),
	        new CustomVertexValue(0.5F, -0.5F, 0, 0x0000FFFF),
	        new CustomVertexValue(-0.5F, -0.5F, 0, 0x00FF00FF),
        };
        base.Initialize();
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice);
        effect.VertexColorEnabled = true;
        base.LoadContent();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
        }

        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は XNA Framework 組み込みの頂点ではなく、ユーザー定義の CustomVertexValue 構造体を用いてプリミティブを描画しています。CustomVertexValue 構造体は IVertexType インターフェイスを実装し、VertexDeclaration プロパティで頂点宣言を返します。

CustomVertexValue 構造体の値は、すべて同じ構造なので、同じ頂点宣言を使えます。個別の値ごとに VertexDeclaration クラスのインスタンスを作成する必要はないため、読み取り専用の静的な VertexDeclaration フィールドを用意し、静的コンストラクタで頂点宣言の初期化を行っています。