忍者ブログ

Memeplexes

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

かんたんXNA4.0 その17 透過パーティクル

3Dゲームでビームや火花、爆発や煙、水しぶきや雪などを表現する方法として
ポイントスプライトがあります。

・・・が、現在XNA4.0からはなくなっています。
DirectX10以降にはポイントスプライトがないのです。

例えばXNA Creators Clubのサンプル、Particle 3D Sampleでは
ポイントスプライトを使って爆発や煙を表現していました。
3DParticles.JPG
この爆発と煙は、実は簡単な小さい画像をたくさん並べたものなのです。
explosion.png(爆発に使われた画像explosion.png)
smoke.png(煙に使われた画像smoke.png)

現在はどうやって描画しているかというと、
四角いポリゴン多数に同じ画像を貼り付けて煙っぽく見せています。
以前この章ではポイントスプライトについて話していたのですが話すことがなくなってしまったので、
透過について話すことにします。



テクスチャを貼る

Creators ClubのサンプルParticle 3D Sampleのexplosion.pngを表示してみましょう。
explosion.png
(ちなみに、背景が黒なのは後で透過処理をするときにそこが透明であると解釈されるからです)


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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionTexture[] vertices = new[]
    { 
        new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
        new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
        new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),

        new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
        new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
        new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
    };
    Texture2D texture;

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            TextureEnabled = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };
        texture = Content.Load<Texture2D>("Content/explosion");
    }

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

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

        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );
        }
    }
}

xna4.0SimplestExplosionTexture.jpg

このプログラムは2つのポリゴンからなる一つの四角形を表示しています。
その四角形にテクスチャを貼り付けているのです。


透過

さて、見事テクスチャが表示できましたが、まだ使い物になりません。
テクスチャに黒い縁がついているからです。
このような四角形をどんなにならべても絶対に炎には見えないでしょう。
(背景が夜なら別ですが)

実用レベルにするには黒い部分を透過することが必要です。
(正確には、黒い部分だけでなく全てを透過します)

透過を行うにはGraphicsDevice.BlendStateプロパティをBlendState.Additiveにします。
色を加算するのです。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionTexture[] vertices = new[]
    { 
        new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
        new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
        new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),

        new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
        new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
        new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
    };
    Texture2D texture;

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            TextureEnabled = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };
        texture = Content.Load<Texture2D>("Content/explosion");
    }

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

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

        GraphicsDevice.BlendState = BlendState.Additive;
        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );
        }
    }
}

xna4.0SimplestExplosionAdditive.jpg

黒い部分がなくなりました!
ただ炎の部分も薄くなっています。
色を背景に加算したからです。

さてここまでは一枚の四角形があるだけでしたが、
もしたくさん四角形があったらどうなるでしょうか?



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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionTexture[] vertices;
    Texture2D texture;

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

        const int particleCount = 20;
        vertices = new VertexPositionTexture[particleCount * 6];

        for (int i = 0; i < particleCount; i++)
        {
            vertices[i * 6 + 0] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
            vertices[i * 6 + 1] = new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0));
            vertices[i * 6 + 2] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));

            vertices[i * 6 + 3] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
            vertices[i * 6 + 4] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));
            vertices[i * 6 + 5] = new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1));

            for (int j = 0; j < 6; j++)
            {
                vertices[i * 6 + j].Position *= 0.5f;
                vertices[i * 6 + j].Position += new Vector3(
                    (float)System.Math.Cos(2 * System.Math.PI * i / particleCount),
                    0,
                    (float)System.Math.Sin(2 * System.Math.PI * i / particleCount)
                    );
            }
        }

    }

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

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

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

        GraphicsDevice.BlendState = BlendState.Additive;
        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );
        }
    }
}


xna4.0SimplestExplosionAdditiveCircler.jpg
ここでは、20個の四角形で円を作り、透過して描画してあります。
ゲームでミニラの放射熱線を描画したいのなら
こんな感じにするといいかもしれません。

さて、これには明らかにおかしなところがあります。
それは円の左側が上手く表示されなくなっているというところです。

奥のポリゴンが手前のものに隠れているのです。
これはデプス・バッファ(深度バッファ)がすでに手前のものになっているためです。

どういうことかというと、これは深度バッファの仕組みに関係しています。
3D空間では手前に物がある場合、より奥のものは見えません。
(もしそうでなければX線CTやMRIは必要ありません!
ついでに言うと、これは2Dでも1Dでも言える話です)


これをコンピュータで実現するために、
ピクセルは3つの色のデータに加えて、その点のカメラからの距離(深度)の情報を持っています。
(と言っても実際の距離ではなく、相対的な、0.0から1.0までの値ですが)

それぞれのピクセルを描画するたびに、深度バッファを更新していき、
もし今描画しようとしている点の深度が深度バッファの値よりも大きければ
(すなわちその点の手前に既に物があるのなら)
描画しないようになっています。

そうすれば奥のものは手前のものにうまく隠されることになります。

ここで問題になっているのはデプス・バッファのこの性質なのです。

いくらテクスチャが透過されるとはいえ、深度バッファが更新されないわけではありません。
もう既に描いた四角形の奥に新たに四角形を描画しようとすると、
深度バッファがその四角形の深度よりも小さいため、
手前のものに隠れてしまうと解釈されて、描画されなくなってしまうのです。

この問題はどうやって解決すればいいのでしょうか?

1つの方法として、描画するときに深度バッファを更新しないと言う方法があります。
そうすれば奥のものが隠れることはなくなるでしょう。

XNA Creators SampleのParticle 3D Sampleで行われているのはこの方法です。

深度バッファの更新を一時的にストップするには、
GraphicsDevice.DepthStencilStateプロパティをDepthStencilState.Noneにします。
(用が終わったらまたDepthStencilState.Defaultに戻してあげましょう。
でないと透過を使っていないほかの部分がおかしなことになるはずです。
このサンプルでは問題ないですが)


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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionTexture[] vertices;
    Texture2D texture;

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

        const int particleCount = 20;
        vertices = new VertexPositionTexture[particleCount * 6];

        for (int i = 0; i < particleCount; i++)
        {
            vertices[i * 6 + 0] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
            vertices[i * 6 + 1] = new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0));
            vertices[i * 6 + 2] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));

            vertices[i * 6 + 3] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
            vertices[i * 6 + 4] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));
            vertices[i * 6 + 5] = new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1));

            for (int j = 0; j < 6; j++)
            {
                vertices[i * 6 + j].Position *= 0.5f;
                vertices[i * 6 + j].Position += new Vector3(
                    (float)System.Math.Cos(2 * System.Math.PI * i / particleCount),
                    0,
                    (float)System.Math.Sin(2 * System.Math.PI * i / particleCount)
                    );
            }
        }

    }

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

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

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

        GraphicsDevice.BlendState = BlendState.Additive;
        GraphicsDevice.DepthStencilState = DepthStencilState.None;
        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );
        }
    }
}





xna4.0SimplestExplosionAdditiveCirclerClean.jpg
上手くいきました!

ともかく、これをいろんな風に動かしていくことによって
爆発や煙、水滴や雪などを描画できるようになるのです。

拍手[0回]

PR

かんたんXNA4.0 その16 ワイヤーフレーム

ここでは、表示する図形の骨組みだけ描画するワイヤーフレームの解説をします。

ここまでは、三角形を描画するときに3つの辺に囲まれた面も描画していましたが、
ワイヤーフレームと言って、3つの辺だけ描画することも可能です。

他の多くの場合と同様、見た方が速いでしょう。

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionColor[] vertices = new[]
    { 
        new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
        new VertexPositionColor(new Vector3(1, 0, 0), Color.Red),
        new VertexPositionColor(new Vector3(-1, 0, 0), Color.Blue),

        new VertexPositionColor(new Vector3(0, 1, -1), Color.White),
        new VertexPositionColor(new Vector3(1, 0, -1), Color.Red),
        new VertexPositionColor(new Vector3(-1, 0, -1), Color.Blue),
    };

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

    protected override void LoadContent()
    {
        effect = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };

        GraphicsDevice.RasterizerState = new RasterizerState
        {
            FillMode = FillMode.WireFrame
        };
    }

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

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>
            (
                PrimitiveType.TriangleList,
                vertices,
                0, 
                vertices.Length / 3
            );
        }
    }
}

xna4.0SimplestWireFrame.jpg
(画像が小さくて少しわかりにくいですが、線で三角形が2つ描かれています。)

このサンプルでは、同じ大きさの三角形を手前に1つ、奥に1つ表示しています。
注目すべきなのは、これがワイヤーフレームで、骨組みしか表示されていないと言うことです。

今までのように普通に三角形を表示していたら、奥の三角形は見えないはずです(説明しませんでしたが、見えないのです)。
しかしワイヤーフレームを使えば、奥の図形も見ることが出来るのです。

また、線しか書かないので描画が非常に高速です。
この性質を利用して3DCGのエディタでは使われることがあるようです。

このワイヤーフレームを実現するためには、GraphicsDeviceのRasterizerStateプロパティに
FillModeをFillMode.WireFrameにセットしたRasterizerStateオブジェクトをセットします。
FillMode列挙体には2つのメンバがあり、
FillMode.WireFrameは線だけを描画し、
FillMode.Solidは図形を完全に描画します。


(さて、このワイヤーフレーム、ゲームで一体どう使えばいいのでしょうか?
ワイヤーフレームは描画が高速で昔から使われていたそうですが、それだけに見栄えが悪いものです。
ほとんど使い道が無いような気もしますが、
ゲームで使う建物の地図なんかの表示に、もしかしたら使えるかもしれません。
(建物の地図は奥が見えていた方がいいはずですからね)
あるいは、プレイヤーが透視する能力を持っていると言う設定にして、
能力を使ったときだけワイヤーフレームにすると言うのもありかもしれません。)

拍手[0回]


かんたんXNA4.0 その15 かんたんなライティング

ここではBasicEffectを使って簡単なライティングを行います。
ライティングというのは光の当たり具合によって明るくしたり暗くしたりすることです。
赤道直下なら太陽の光がもろに当たるため明るくなりますが、
北極や南極はほとんど当たらないため暗くなります。

ここでは、また違った頂点、VertexPositionNormalTextureを使います。
(できればもっとシンプルな"VertexPositionNormalColor"みたいなものを使いたいのですが、
残念ながらXNAには用意されていません。)


これは新たに法線(ノーマル:面に対して垂直な線)のデータを持っています。
跳ね返る光の強さを(内部で)計算して光の効果を出すのに必要だからです。
(法線を忘れてしまったという人のために少し直感的な説明をしておきます。
まずは鏡を用意してください。手鏡でも洗面所の鏡でもかまいません。
そして鏡の中のあなたの目を見てください。
鏡の中の目から鏡の外のあなたの本物の目を結んだ直線がその鏡の法線です。
(そう、別に目でなくても、鏡の中の何かと鏡の外の本物を結べばなんでも法線になります。)
鏡でないものの法線を考えたいときには、その物体が鏡であると考えて同じ事をすると法線が求まります。)


理屈の上では法線(ノーマル)は頂点の座標データから自動的に計算することが出来るのですが、
描画の最中にそんなことをしていてはパフォーマンスが低下してしまいますし、
法線を利用した裏ワザ的なものも出来なくなってしまうので、
最初に手作業で設定してやらなければならないことになっているのでしょう。(多分)

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices = new[]
    { 
        new VertexPositionNormalTexture
        (
            new Vector3(0, 1, 0),
            new Vector3(0, 0, 1),
            new Vector2(0.5f, 0)
        ),
        new VertexPositionNormalTexture
        (
            new Vector3(1, 0, 0),
            new Vector3(0, 0, 1),
            new Vector2(1, 1)
        ),
        new VertexPositionNormalTexture
        (
            new Vector3(-1, 0, 0),
            new Vector3(0, 0, 1),
            new Vector2(0, 1)
        ),
 
    };
    Texture2D texture;

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            TextureEnabled = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };
        effect.EnableDefaultLighting();
        texture = Content.Load<Texture2D>("Content/Penguins");
    }

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

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

        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>
            (
                PrimitiveType.TriangleList,
                vertices,
                0, 
                vertices.Length / 3
            );
        }
    }
}

xna4.0SimplestLighting.jpg
このサンプルでは各頂点に法線を設定し、ライティングしています。
霞がかって左側が暗くなっています。

参考までにライティング無しだとこうなります。
xna4.0SimplestTexture3D.jpg

BasicEffect.EnableDefaultLightingを忘れないでください。
これを呼ばないと上の図のようにライティングが無しになってしまいます。
このメソッドは自動的にいい感じにライトを設定してくれます。
(いい感じの照明をするのは本当は難しい設定が必要ですが、このメソッドはそれを自動的にしてくれるのです)

拍手[0回]


かんたんXNA4.0 その14 テクスチャ三角形

これまで描いてきた図形は全て頂点に色が付いていただけでしたが、
もちろんXNAではテクスチャが描かれた図形を表示することも出来ます。

テクスチャ付きの図形を表示するには、頂点にVertexPositionTexture構造体を使います。
これは色のデータの代わりに、テクスチャのデータ(Vector2)を持っています。

そのデータは1.0 × 1.0のテクスチャ座標上の点を表していて、
表示するテクスチャのどの部分を頂点が表示するかを意味します。

ここら辺はサンプルとその結果を見た方が速いかもしれません。
 

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionTexture[] vertices = new[]
    { 
        new VertexPositionTexture(new Vector3(0, 1, 0), new Vector2(0.5f, 0)),
        new VertexPositionTexture(new Vector3(1, 0, 0), new Vector2(1, 1)),
        new VertexPositionTexture(new Vector3(-1, 0, 0), new Vector2(0, 1)),
    };
    Texture2D texture;

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            TextureEnabled = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };
        texture = Content.Load<Texture2D>("Content/Penguins");
    }

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

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

        effect.Texture = texture;

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0, 
                vertices.Length / 3
                );
        }
    }
}

 xna4.0SimplestTexture3D.jpg
このサンプルでは三角形の中に"Penguins"という画像を表示しています。
3つのそれぞれの頂点は、座標と、画像のどの部分を担当するかのデータを持っています。

そのデータは、画像のサイズを1.0 × 1.0であると考えたときのテクスチャの点の座標で、
その方向は普通のスクリーンの座標と同じように、左上が(0, 0)となっています。

xna4.0SimplestTexture3DWithCoordinates.jpg
この方法を使えば、複雑なマッピングも行うことが出来ます。
例えば世界地図を球にマップして地球を作ることも出来るのです。

もちろんこの三角形は3Dですからカメラの位置を調節すると遠近感が出ます。
xna4.0SimplestTexture3DWithCameraMoved.jpg
(斜めから見た図)


さて、テクスチャを使った三角形を描画する上で忘れがちなのは、
BasicEffect.TextureEnabledプロパティをtrueにセットすることです。
これによってBasicEffectのテクスチャの描画が有効になります。

もしそうしなければ、BasicEffectが描画の時にテクスチャを考慮に入れなくなり、
真っ白の三角形が描かれるのみとなります。

xna4.0SimplestTexture3DWithTextureDisabled.jpg

これをする理由は色付きの三角形を表示するときにBasicEffect.VertexColorEnabledプロパティをtrueにした理由と同じです。
こういったプロパティによって、BasicEffectは内部の描画モードを変更しているのです。

拍手[0回]


かんたんXNA4.0 その13 IndexBuffer

頂点にパフォーマンスを上げるVertexBufferがあるのと同じように
インデックスにもパフォーマンスを上げる(しかしめんどくさい)IndexBufferがあります。
 

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionColor[] vertices = new[]
    { 
        new VertexPositionColor(new Vector3(1, 0, 0), Color.White),
        new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red),
        new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue),
        new VertexPositionColor(new Vector3(0, -1, 0), Color.Blue),
    };
    VertexBuffer vertexBuffer;
    int[] indices = new[]
    { 
        0, 1, 2,
        0, 3, 1
    };
    IndexBuffer indexBuffer;

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

    protected override void LoadContent()
    {
        effect = 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     //カメラからこれより遠い物体は画面に映らない
            )
        };

        vertexBuffer = new VertexBuffer(
            GraphicsDevice, 
            typeof(VertexPositionColor), 
            vertices.Length,
            BufferUsage.None
            );
        vertexBuffer.SetData<VertexPositionColor>(vertices);

        indexBuffer = new IndexBuffer(
            GraphicsDevice,
            IndexElementSize.ThirtyTwoBits,
            indices.Length,
            BufferUsage.None
            );
        indexBuffer.SetData<int>(indices);
    }

    protected override void UnloadContent()
    {
        effect.Dispose();
        vertexBuffer.Dispose();
        indexBuffer.Dispose();
    }

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

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

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            GraphicsDevice.DrawIndexedPrimitives(
                PrimitiveType.TriangleList,
                0,  //baseIndex
                0,  //minVertexIndex
                vertices.Length,    //numVertices
                0,  //startIndex
                indices.Length / 3  //primitiveCount
                );
        }
    }
}

 

xna4.0SimplestIndexBuffer.jpg
見た目は普通に描画したときと全く変わりませんが、内部ではIndexBufferを使っており、
ほんの少しパフォーマンスがよくなっているはずです。
(しかしやはり、こういう単純な例では全くありがたみがありません。)
 

拍手[0回]