WisdomSoft - for your serial experiences.

頂点配列とポリゴン

3Dグラフィックスは、空間内にある頂点を結び、基本的な図形(プリミティブ)を描画するところから始まります。代表的なプリミティブは 3 つの頂点を繋ぎ合わせた三角形であり、複数の三角形を組み合わせて複雑なモデルを表現します。

頂点の描画

2D グラフィックスの場合、ビットマップやパスなどで保存されている画像データと、描画するデバイスのピクセルの関係が 1 対 1 なので、非常に分かりやすい構造でした。ところが、3D グラフィックスの場合は、データが 3 次元空間の立体的な情報を持っていても、描画するデバイスは平面のディスプレイとなります。そのため、3D グラフィックスを表示するには、立体的な 3D モデルをピクセルに変換しなければなりません。この工程を理解することが重要になります。

3D グラフィックスは、頂点と呼ばれる 3 次元空間上の点を組み合わせて作られます。頂点は、互いに結びあって線や三角形といった図形を構成します。さらに、三角形が複雑に組み合わさって、キャラクターや建物が作られます。これが、いわゆるポリゴンです。よって、頂点は 3D グラフィックスで最も原子的なデータとなります。

複数のポリゴンによって構築された複雑な物体をプリミティブと呼びます。ゲーム画面に表示されるプリミティブは、分解していけば複数のポリゴンの組み合わせであり、ポリゴンは複数の頂点を結んだ三角形となります。3D グラフィックスを理解するために、プリミティブを描画する仕組みから説明します。

プリミティブを描画するには GraphicsDevice クラスの DrawUserPrimitives() メソッドを使います。通常、Game クラスの Draw() メソッドの流れの中でこのメソッドを呼び出します。

GraphicsDevice クラス DrawUserPrimitives() メソッド
public void DrawUserPrimitives<T> (
         PrimitiveType primitiveType,
         T[] vertexData,
         int vertexOffset,
         int primitiveCount
) where T : ValueType

このメソッドの型パラメータ T には、頂点の型を指定します。頂点の型は必要に応じて柔軟にフォーマットを変更できます。詳細は後述するので、この場では XNA Framework で既定で用意されている組み込みの頂点型を使いましょう。

primitiveType パラメータには頂点の種類を、vertexData パラメータには頂点の配列を指定します。DrawUserPrimitives() メソッドは primitiveType パラメータに指定された方法に従って、vertexData パラメータに指定された頂点を結びます。vertexOffset パラメータには、vertexData パラメータに指定した配列から頂点の読み取りを開始するオフセットを、primitiveCount パラメータには、描画するプリミティブの数を指定します。

primitiveType パラメータに指定する値は、プリミティブの種類を表す Microsoft.Xna.Framework.Graphics.PrimitiveType 列挙型のいずれかのメンバです。

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

PrimitiveType 列挙型のメンバは、頂点データをどのように結ぶかを表します。DrawUserPrimitives() メソッドは primitiveType パラメータに指定された値に従って、頂点を点、線、三角形などで結んで描画します。プリミティブの種類によって、どのように頂点が結ばれるかは表1のようになります。

表1 プリミティブの種類
メンバ 意味
LineList 頂点列を 2 点からなる独立した線分として扱う PointList
LineStrip 頂点列を連続した線分として扱う PointList
TriangleList 頂点列を 3 点からなる独立した三角形として扱う PointList
TriangleStrip 頂点列を連続した三角形として扱う PointList

描画できるプリミティブの数は、頂点列の要素数と種類によって決定します。例えば、LineList であれば 2 つの頂点で 1 つのプリミティブを構成するため、プリミティブの数は頂点列の要素数の半分になります。TriangleList の場合 3 つの頂点で 1 つのプリミティブを構成します。もちろん、vertexData に指定した配列のすべての要素を使わなければならないということはありません。オフセットと合わせて、配列内の一部の要素だけをプリミティブとして描画しても構いません。

GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, data, 0, 1);

上記の DrawUserPrimitives() メソッドの呼び出しの場合、頂点の配列 data から 1 つの三角形を描画します。1 つの三角形は 3 つの頂点で構成されるため、data 配列は少なくとも 3 つ以上の要素を持たなければなりません。

頂点の型

ここで、DrawUserPrimitives() メソッドに渡す頂点の配列 T 型をどのような型にするべきかが問題になります。頂点は、プリミティブの描画に必要な最小限の情報だけで構成できるように、目的によってフォーマットを選択できます。頂点には、位置を表す座標の他にも、必要に応じて色などの情報を含めることもありますが、それらが必要ではない場合もあります。不必要な情報を含めることは無駄になってしまうため、必要な時に、必要な情報だけを頂点として設定するため、このような柔軟なフォーマットの選択が可能になっています。

この場では、XNA Framework で事前に用意されているカスタム頂点フォーマットの Microsoft.Xna.Framework.Graphics.VertexPositionColor 構造体を使います。

Microsoft.Xna.Framework.Graphics.VertexPositionColor 構造体
[SerializableAttribute]
public struct VertexPositionColor : IVertexType

VertexPositionColor 構造体は、頂点の位置と色を含むカスタム頂点フォーマットです。この構造体のコンストラクタに、頂点の座標と色を指定します。

VertexPositionColor 構造体のコンストラクタ
public VertexPositionColor (Vector3 position, Color color)

position パラメータには 3D 空間内の頂点の座標を、color パラメータには頂点の色を指定します。

コンストラクタで指定した座標と色はフィールドに設定されています。必要に応じて、フィールドから値を設定または取得できます。頂点の座標は Position フィールド、頂点の色は Color フィールドで公開されます。

VertexPositionColor 構造体 Position フィールド
[SuppressMessageAttribute("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public Vector3 Position
VertexPositionColor 構造体 Color フィールド
[SuppressMessageAttribute("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public Color Color

頂点の値は API を通じてグラフィックデバイスまで送られる情報であるため、値の構造を公開する必要があります。そのため、頂点データは必ず構造体であり、フィールドで公開されなければなりません。SuppressMessageAttribute 属性は Visual Studio の静的分析ツール規則違反「CA1051: 参照できるインスタンス フィールドを宣言しないでください」による警告を抑制するために設定されています。

2D グラフィックスの座標には Vector2 構造体を用いていましたが、3D グラフィックスでは空間内における深さを表す Z 座標が追加された Vector3 構造体を座標に用います。このとき、Z 座標の値がどちらの方向に向いているかは座標系によって異なります。Z 座標の値が高いほど奥に向かう座標系を左手座標系、手前に向かう座標系を右手座標系と呼びます。

図1 左手座標系と右手座標系
図1 左手座標系と右手座標系

DirectX では標準で左手座標系が用いられていましたが、XNA Framework では OpenGL と同じ右手座標系が用いられています。

最初のサンプルでは、頂点の座標変換を行わないでプリミティブを描画します。ゲーム画面の中心を 0.0 として、X 座標と Y 座標それぞれが  -1.0 ~ 1.0 の範囲で描画可能です。これをスクリーン座標と呼びます。

図2 スクリーン座標
図2 スクリーン座標

Z 座標の範囲は 0.0 ~ 1.0 ままでが有効範囲です。ただし、座標変換を行わないので Z 座標の値を変更しても対応するピクセルが同じなので立体に見えることはありません。

vertices = new VertexPositionColor[] {
	new VertexPositionColor(new Vector3(0, 0.5F, 0), Color.Red),
	new VertexPositionColor(new Vector3(0.5F, -0.5F, 0), Color.Blue),
	new VertexPositionColor(new Vector3(-0.5F, -0.5F, 0), Color.Lime),
};

上記のコードは、1 つの三角形を描画するための VertexPositionColor 構造体の配列です。線や三角形のプリミティブを描画するとき、配列の先頭の要素から順にデータが入力されます。例えば、描画する頂点の種類が PrimitiveType.TriangleList である場合、配列の 0 ~ 2 番の要素が最初の三角形、3 ~ 5 番の要素が 2 つ目の三角形というように続きます。

三角形のプリミティブには、表と裏という概念があります。デフォルトの設定では、三角形の表は描画されますが、裏は描画されません。このような、ポリゴンの片面を描画しない処理をカリングと呼びます。頂点を結ぶ順番が時計回りの方向のであれば表、反時計回りであれば裏となります。

図3 プリミティブの表と裏
図3 プリミティブの表と裏

通常、プリミティブによって表面が覆われている物体は裏側を描画する必要がありません。カリングを有効にすることで裏面の描画を省略できます。ゲームなどで視点がキャラクターの表面を突き抜けると、キャラクターが画面から消えて背景が映し出されるのはカリングによるものです。