忍者ブログ

Memeplexes

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

かんたんXNA その30 カスタム頂点

このページは古いです
最新版はこちら

さて、ここまで、頂点データに使う構造体は
あらかじめ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以上でなければなりません)
Rgba64 符号なし16bit整数が4つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。(VertexShaderのバージョンが2.0以上でなければなりません)
Color Color構造体です。
Rgba32 byte4つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。(VertexShaderのバージョンが2.0以上でなければなりません)
Rg32 符号なし16bit整数が2つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。(VertexShaderのバージョンが2.0以上でなければなりません)
NormalizedShort2 符号付き16bit整数が2つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。
NormalizedShort4 符号付き16bit整数が4つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。
Normalized101010 符号付き10bitが3つです。それぞれ小数に展開されて0~1の範囲内の大きさになります。
Short2 符号付き16bit整数が2つです。
Short4 符号付き16bit整数が4つです。
Byte4 unsinged byteが4つです。
Uint101010 符号なし10bitが3つです。
Unused このメンバは特別で、普通は使いません。VertexElementMethod.UVとVertexElementMethod.LookUpPresampledで使われます。

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

[Serializable]
public struct VertexElement


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

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

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


streamは使うデータストリームの数(インデックス)です。
普通は1つだけしか使わないのですが、その場合は0でいいようです。(自信がありません)

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

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

elementMethodはテセレータ(頂点を自動的に増やしてポリゴンを分割し、滑らかにするもの)がどんな風に頂点データを扱うかをあらわします。ふつうはVertexElementMethod.Default(とくに操作は行わない)でしょう。

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がテクスチャ座標になってしまうのですが、これが困るのです。ハードウェアとしては、別々のメンバが同じ利用目的を持っていてはそれぞれの区別が出来ないので困るのです。そこで、問題を解決するためにインデックスを振ります。片方のテクスチャ座標をTexCoord0、もう片方をTexCoord1とでもすれば区別が出来るようになり、問題解決です。この引数に指定するのは、そのインデックスです。普通は0でいいでしょう。



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

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


public struct VertexPositionNormalColor
{
    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 VertexElement[] VertexElements
        = {
            new VertexElement(
                0,
                0,
                VertexElementFormat.Vector3,
                VertexElementMethod.Default,
                VertexElementUsage.Position,
                0
            ),
            new VertexElement(
                0, 
                sizeof(float) * 3,
                VertexElementFormat.Vector3,
                VertexElementMethod.Default,
                VertexElementUsage.Normal,
                0
            ),
            new VertexElement(
                0,
                sizeof(float)*(3 + 3),
                VertexElementFormat.Color,
                VertexElementMethod.Default,
                VertexElementUsage.Color,
                0
            )
        };
}

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

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect basicEffect;

    VertexPositionNormalColor[] vertices = new VertexPositionNormalColor[3];

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

        vertices[0].Position = new Vector3(0, 1, 0);
        vertices[0].Color = Color.Red;
        vertices[0].Normal = new Vector3(0, 0, 1);

        vertices[1].Position = new Vector3(1, 0, 0);
        vertices[1].Color = Color.Red;
        vertices[1].Normal = new Vector3(0, 0, 1);

        vertices[2].Position = new Vector3(-1, 0, 0);
        vertices[2].Color = Color.Red;
        vertices[2].Normal = new Vector3(0, 0, 1);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                (float)Window.ClientBounds.Width / Window.ClientBounds.Height,
                1, 100
                );
            basicEffect.VertexColorEnabled = true;
            basicEffect.EnableDefaultLighting();
        }
    }

    protected override void Update(GameTime gameTime)
    {
        basicEffect.World *= Matrix.CreateRotationY(0.05f);
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionNormalColor.VertexElements
            );

        basicEffect.Begin();

        foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        {
            pass.Begin();

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

            pass.End();
        }

        basicEffect.End();
    }
}

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

頂点バッファを使うのなら、頂点のサイズを表すメンバが必要になるでしょう。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public struct VertexPositionNormalColor
{
    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 VertexElement[] VertexElements
        = {
            new VertexElement(
                0,
                0,
                VertexElementFormat.Vector3,
                VertexElementMethod.Default,
                VertexElementUsage.Position,
                0
            ),
            new VertexElement(
                0, 
                sizeof(float) * 3,
                VertexElementFormat.Vector3,
                VertexElementMethod.Default,
                VertexElementUsage.Normal,
                0
            ),
            new VertexElement(
                0,
                sizeof(float)*(3 + 3),
                VertexElementFormat.Color,
                VertexElementMethod.Default,
                VertexElementUsage.Color,
                0
            )
        };

    public static readonly int SizeInBytes = sizeof(float) * (3 + 3) + sizeof(byte) * 4;
}


使う側はこんな感じです。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect basicEffect;

    VertexPositionNormalColor[] vertices = new VertexPositionNormalColor[3];
    VertexBuffer vertexBuffer;

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

        vertices[0].Position = new Vector3(0, 1, 0);
        vertices[0].Color = Color.Red;
        vertices[0].Normal = new Vector3(0, 0, 1);

        vertices[1].Position = new Vector3(1, 0, 0);
        vertices[1].Color = Color.Red;
        vertices[1].Normal = new Vector3(0, 0, 1);

        vertices[2].Position = new Vector3(-1, 0, 0);
        vertices[2].Color = Color.Red;
        vertices[2].Normal = new Vector3(0, 0, 1);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                (float)Window.ClientBounds.Width / Window.ClientBounds.Height,
                1, 100
                );
            basicEffect.VertexColorEnabled = true;
            basicEffect.EnableDefaultLighting();

            vertexBuffer = new VertexBuffer(
                graphics.GraphicsDevice,
                vertices.Length * VertexPositionNormalColor.SizeInBytes, 
                ResourceUsage.None
                );
            vertexBuffer.SetData(vertices);
        }
    }

    protected override void Update(GameTime gameTime)
    {
        basicEffect.World *= Matrix.CreateRotationY(0.05f);
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionNormalColor.VertexElements
            );
        graphics.GraphicsDevice.Vertices[0].SetSource(
            vertexBuffer,
            0,
            VertexPositionNormalColor.SizeInBytes
            );

        basicEffect.Begin();

        foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        basicEffect.End();
    }
}


拍手[0回]

PR