忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

かんたんXNA4.0 その29 カスタム頂点

さて、ここまで、頂点データに使う構造体は
あらかじめXNAに用意されたものを使っていました。

XNAに初めから用意されている頂点の構造体には
以下の4つがあります。
それぞれの名前はそのメンバを反映しています。
(いずれもMicrosoft.Xna.Framework.Graphics名前空間にあります)

名前 座標 テクスチャ座標 法線
VertexPositionColor × ×
VertexPositionColorTexture ×
VertexPositionNormalTexture ×
VertexPositionTexture × ×

しかし、ManagedDirectXでは14個用意されていたことを考えると、
「以下の4つがあります」というより
「以下の4つしかありません」という方が適切でしょう。
だいたいVertexPositionNormalColorが無いってどういうことですか!
初学者を殺す気ですか!?

しかしながら用意されていないからといって泣き寝入りする必要はありません。
頂点の構造体は自分で新たに作ることが出来るからです。
VertexPositionNormalColorはもちろん、
さまざまなタイプの頂点を作ることが出来るのです。

といっても頂点のメンバの型は何でもいいわけではなく、
Microsoft.Xna.Framework.Graphics.VertexElementFormat列挙体
で定義された型のものだけに限られます。
(型というか正確には「メモリ上にどうデータが入るか」です。
メモリの配置が同じなら型は同じでなくてもかまいません。)

まぁstringやDateTimeをメンバに持った頂点を作ってもあまり意味無いでしょうしね。

public enum VertexElementFormat

メンバ
メンバ名 説明
Single floatです。(32bit)
Vector2 Vector2構造体です。
Vector3 Vector3構造体です。
Vector4 Vector4構造体です。
HalfVector2 16bitの浮動少数点数が2つです。(VertexShaderのバージョンが2.0以上でなければなりません)
HalfVector4 16bitの浮動小数点数が4つです。(VertexShaderのバージョンが2.0以上でなければなりません)
Color Color構造体です。
NormalizedShort2 符号付き16bit整数が2つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。
NormalizedShort4 符号付き16bit整数が4つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。
Short2 符号付き16bit整数が2つです。
Short4 符号付き16bit整数が4つです。
Byte4 unsinged byteが4つです。

以上のデータを頂点の構造体のメンバにすることができます。
構造体のメンバを定義したら、今度は
Microsoft.Xna.Framework.Graphics.VertexElement構造体の
配列を作る必要があります。

[Serializable]
public struct VertexElement


これは頂点の構造体がどんなメンバを持っているかを意味していて、
ハードウェアが頂点データを扱うのに役立ちます。
つまり、VertexPositionColor.VertexElements静的フィールドのようなものを
自分で作る必要があるのです。

コンストラクタはかなりフクザツです。

public VertexElement (
        short offset,
        VertexElementFormat elementFormat,
        VertexElementUsage elementUsage,
        byte usageIndex
)


offsetはメンバの構造体内での位置です。(バイト単位)
最初のメンバはこれは0で、次のメンバのは最初のメンバのサイズで、
その次のは・・・という具合にどんどん増えていきます。

elementFormatはデータのサイズを意味する列挙体、
先ほど説明したVertexElementFormatです。

elementUsageは最重要で、これはデータの利用目的です。PositionとかColorとかNormalとかそういうやつです。これは単なる印、C#でいう属性のようなもので、ここで決めたとおりにメンバを使う必要は必ずしもありませんが、特に理由がない限り正直に入力すべきでしょう。保守が難しくなるかもしれませんし。

VertexElementUsage
メンバ名 説明
Position  頂点の位置です。
Normal  法線です。
Color  色です。UsageIndexが0の時にはディフューズの色を表し、UsageIndexが1の時にはスペキュラの色を表します。
TextureCoordinate  テクスチャの座標です。
PointSize  ポイントスプライトの大きさです。
Depth  深度です。
Sample  サンプラーデータです。
Fog  フォグに使うデータです。
Binormal  従法線ベクトル(接ベクトル×法線ベクトル、接線と法線の両方に対して垂直です)データです。
BlendIndices  ブレンディングのインデックスのデータです。
BlendWeight  ブレンディングに使われる重みのデータです。
Tangent  接線ベクトルです。
TessellateFactor  テセラレーションに使われる浮動小数点数です。


usageIndexは1つの構造体の中で、別々のメンバが同じVertexElementUsageを指定した場合に、お互いを識別するために使います。例えばポリゴンに複数のテクスチャを使いたくなったとしましょう。そういった場合、両方ともelementUsageがTextureCoordinateになってしまうのですが、これが困るのです。ハードウェアとしては、別々のメンバが同じ利用目的を持っていてはそれぞれの区別が出来ないので困るのです。そこで、問題を解決するためにインデックスを振ります。片方のテクスチャ座標をTexCoord0、もう片方をTexCoord1とでもすれば区別が出来るようになり、問題解決です。この引数に指定するのは、そのインデックスです。普通は0でいいでしょう。

こうして作ったVertexElementの配列をVertexDeclarationのコンストラクタに突っ込みます。
出来たVertexDeclarationのインスタンスはIVertexType.VertexDeclarationプロパティで返してあげましょう。
この「頂点の宣言」はGraphicsDeviceがポリゴンを描画するのに必要なのです。



で、VertexPositionNormalColorを作るとしたらこんな感じになります。

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


public struct VertexPositionNormalColor:IVertexType
{
    public Vector3 Position;
    public Vector3 Normal;
    public Color Color;

    public VertexPositionNormalColor(Vector3 position, Vector3 normal, Color color)
    {
        this.Position = position;
        this.Normal = normal;
        this.Color = color;
    }

    public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
        (
            new VertexElement(
                0,
                VertexElementFormat.Vector3,
                VertexElementUsage.Position,
                0
            ),
            new VertexElement(
                sizeof(float) * 3,
                VertexElementFormat.Vector3,
                VertexElementUsage.Normal,
                0
            ),
            new VertexElement(
                sizeof(float) * (3 + 3),
                VertexElementFormat.Color,
                VertexElementUsage.Color,
                0
            )
        );

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

これを実際に使うのはあらかじめ用意された頂点のやり方と変わりません。

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect basicEffect;

    VertexPositionNormalColor[] vertices =
    {
        new VertexPositionNormalColor(new Vector3(0, 1, 0),new Vector3(0, 0, 1), Color.Red),
        new VertexPositionNormalColor(new Vector3(1, 0, 0),new Vector3(0, 0, 1), Color.Red),
        new VertexPositionNormalColor(new Vector3(-1, 0, 0),new Vector3(0, 0, 1), Color.Red)
    };

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
    }

    protected override void LoadContent()
    {
        basicEffect = new BasicEffect(GraphicsDevice)
        {
            VertexColorEnabled = true,
            View = Matrix.CreateLookAt
            (
                new Vector3(0, 0, 3),  //カメラの位置
                new Vector3(0, 0, 0),   //カメラの見る点
                new Vector3(0, 1, 0)    //カメラの上向きベクトル
            ),
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };

        basicEffect.EnableDefaultLighting();
    }

    protected override void UnloadContent()
    {
        basicEffect.Dispose();
    }

    protected override void Update(GameTime gameTime)
    {
        basicEffect.World *= Matrix.CreateRotationY(MathHelper.ToRadians(1));
    }

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

        foreach (var pass in basicEffect.CurrentTechnique.Passes)
        {
            pass.Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionNormalColor>
            (
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
            );
        }
    }
}

vertexPositionNormalColor1.JPGvertexPositionNormalColor2.JPG
このサンプルでは法線付きの赤い三角形を回転させています。
角度によって明るさが変わるのがわかると思います。
これは法線があるからです。
法線のないVertexPositionColorではライティングに意味はありません。
法線つきのVertexPositionNormalColorを作ることによって、
ライティングの出来る色つきポリゴンを作ったのです。

拍手[0回]

PR