忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


かんたんXNA4.0 その10 3D四角形

ここでは少し話を戻して普通の四角形を表示します。

四角形を表示するためには三角形を2つ組み合わせます。

2triangles.JPG
この調子でどんな複雑な図形も
三角形を組み合わせることで描画できます。
 

 


四角形の描画

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.Blue),
        new VertexPositionColor(new Vector3(1, 0, 0), Color.White),
        new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red),

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

    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     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

    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,  //vertexOffset
                vertices.Length / 3 //primitiveCount
                );
        }
    }
}

xna4.0SimplestRectangle.jpg

このプログラムは6つの頂点を使って2つの三角形を描いています。

MyGame.DrawメソッドのDrawUserPrimitivesに注目してください。
このprimitiveCount引数が1から2に変わっています。
これは三角形の数を1から2に変えたからです。

さて、このコードには気持ち悪いところがあります。
頂点のデータの重複です。
四角形を書くのに必要な頂点は4つです。
6つも必要ありません。2つは無駄です。

重複は良いプログラムの天敵です。
何とかして取り除かなければなりません。

そうするにはGraphicsDevice.DrawUserIndexedPrimitivesメソッドを使います。
これを使うと、頂点データは4つだけで済みます。
そのかわり、四角形を構成する三角形の
頂点のインデックスを6つ指定してやります。

ここで言うインデックスというのは、4つのうち
何番目の頂点を使うのかということです。
頂点データの代わりにインデックスが2つの三角形を指定するのです。
 

 


インデックス

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),
    };
    int[] indices = new[]
    {
        0, 1, 2,
        0, 3, 1
    };

    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     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

    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.DrawUserIndexedPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length, 
                indices,
                0,
                indices.Length / 3
                );
        }
    }
}

これも、全く同じ四角形を描画します。

違うのはインデックスを使っているという点です。
xnaTriangleWithIndices.JPG
このように、インデックスを使うと
頂点データの重複をなくすことが出来ます。

 

拍手[0回]

PR

かんたんXNA4.0 その9 キーボード入力

ここまではアニメーションを行いましたが
これだけではもちろんゲームになりません。

ゲームはプレイヤーの入力に反応してこそゲームです。
そうでなければよく出来たデジタルアニメーションに過ぎません。

XNAの入力する方法には
1.ゲームパッド
2.キーボード
3.マウス
の3つがあります。

ここではキーボード入力をあつかいます。
(他の2つも似たようなものです。インテリセンスの助けがあれば問題なく出来ます。)
 


簡単な例

Xnaで入力を扱うには、1秒に60回、
Game.Updateメソッド内でいちいち入力機器
の状態を調べてやると言う方法を取ります。
入力機器を表すユーティリティクラスから、
入力機器の状態を表す構造体を取得します。

入力機器の状態を表す構造体は*****Stateという名前になっていて、
キーボードの場合はMicrosoft.Xna.Framework.Input.KeyboardState構造体です。

public struct KeyboardState

この構造体にはキーの状態を取得するメソッド、IsKeyDownメソッドと、IsKeyUpメソッドがあります。

public bool IsKeyDown ( Keys key )
public bool IsKeyUp( Keys key )


IsKeyDownメソッドはキーが押されていればtrue, 押されていなければfalseを返し、
IsKeyUpメソッドはキーが押されていなければtrue, 押されていればfalseを返します。
引数はキーボードの特定のキーを表す列挙型、Microsoft.Xna.Framework.Input.Keysです。

Keys列挙型のメンバは多いのでここに書くことはしませんが、
たとえばキーのAボタンが押されているかを調べるには "Keys.A" というふうに書きます。

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

public class MyGame : Game
{
    protected override void Update(GameTime gameTime)
    {
        KeyboardState keyboardState = Keyboard.GetState();

        if (keyboardState.IsKeyDown(Keys.Space))
        { Window.Title = "Space key is pressed."; }
        else
        { Window.Title = ""; }
    }
}

このサンプルを実行すると、スペースキーが押されたときだけウィンドウのタイトルが変わります。
これは、1秒間に60回、スペースキーの状態を調べてウィンドウのタイトルを設定しなおしているのです。
普通のアプリケーションがイベントを使ってキーの状態を調べるのとは大きく違います。


カメラを動かす

もうすこし実際のゲームで使われるのに近いコードにしてみましょう。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

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

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            VertexColorEnabled = true,
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

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

    protected override void Update(GameTime gameTime)
    {
        KeyboardState keyboardState = Keyboard.GetState();

        if(keyboardState.IsKeyDown(Keys.Left))
        {
            angle -= MathHelper.ToRadians(0.5f);//1/60秒に0.5°回転
        }
        if(keyboardState.IsKeyDown(Keys.Right))
        {
            angle += MathHelper.ToRadians(0.5f);//1/60秒に0.5°回転
        }
    }

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

        effect.View = Matrix.CreateLookAt
        (  
            new Vector3
            (
                3 * (float)Math.Sin(angle),
                0,
                3 * (float)Math.Cos(angle)
            ),  //カメラの位置
            new Vector3(0, 0, 0),   //カメラの見る点
            new Vector3(0, 1, 0)    //カメラの上向きベクトル
        );

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

            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,//vertexOffset
                1 //primitiveCount
                );
        }
    }
}

このプログラムは「←」キーと「→」キーでカメラの位置を操作しています。
xna4.0SimplestKeyboardInput0.jpg
最初の状態

xna4.0SimplestKeyboardInputLeft.jpg
「←」キーを押したとき。カメラは左に。

xna4.0SimplestKeyboardInputRight.jpg
「→」キーを押したとき。カメラは右に。

拍手[1回]


かんたんXNA4.0 その8 3Dカメラのアニメーション

前回は2Dでしたが今回は
3Dカメラのアニメーションを行います。

これもやはりGame.Updateメソッド内で
カメラの座標と向きを変えることによって
アニメーションを行っています。
 

 


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

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

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            VertexColorEnabled = true,
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

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

    protected override void Update(GameTime gameTime)
    {
        angle += MathHelper.ToRadians(0.5f);//1/60秒に0.5°回転
    }

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

        effect.View = Matrix.CreateLookAt
        (  
            new Vector3
            (
                3 * (float)Math.Sin(angle),
                0,
                3 * (float)Math.Cos(angle)
            ),  //カメラの位置
            new Vector3(0, 0, 0),   //カメラの見る点
            new Vector3(0, 1, 0)    //カメラの上向きベクトル
        );

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

            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,//vertexOffset
                1 //primitiveCount
                );
        }
    }
}

このサンプルは三角形の周りをカメラがぐるぐる回ることによってアニメーションを行っています。
(三角形が回っているように見えますが、実際に回っているのはカメラの方です。
つまり天動説ではなく地動説というわけです。)


xna4.0SimplestTriangle3DWithCameraRotating1.jpg
xna4.0SimplestTriangle3DWithCameraRotating2.jpg
xna4.0SimplestTriangle3DWithCameraRotating3.jpg

ただし、これ以上回転して、裏から見ると三角形は見えなくなります。
xna4.0SimplestTriangle3DWithCameraRotating4.jpg

これはXNAが、
座標が時計回りの順番になっている三角形しか描画しない
からです。
xnaCullingDescription.JPG
回転して裏から見ると反時計回りの順番になっているように見え、
描画されなくなるのです。
(もちろん、さらに回転して元の時計回りの順番に見えるようになったら
再び描画されるようになります。)
これはパフォーマンスの問題で、
これによって描画が高速化するようです。


どうしても裏から見たときも描画したいという場合は、
GraphicsDeviceにRasterizerState.CullModeCullMode.Noneに設定したオブジェクトをセットしてやると
裏側も描画されるようになります。
(下のコードをLoadContentかDrawメソッドの内部にでも追加してみてください)

        GraphicsDevice.RasterizerState = RasterizerState.CullNone;


xna4.0SimplestTriangle3DWithCameraRotatingCullModeNone.jpg 
(ちなみに、RasterizerState.CullModeのデフォルトは
CullMode.CullCounterClockwiseFaceで、反時計回りは描画しないという意味です。
CullModeは全部で3つあり、
1つがデフォルトのCullCounterClockwiseFace、
2つ目がここで使ったNone(「描画しないということは無い」という意味)、
3つ目はCullClockwiseFaceで、デフォルトの逆です。)

拍手[2回]


かんたんXNA4.0 その7 アニメーション

ゲームで弾やキャラクターを動かすことを考えましょう。

一番簡単な方法は、一秒間に60回呼ばれているGame.Drawメソッドの中で
座標データを少しずつ変えて表示することです。

この方法は上手くいきます。

Drawメソッドの中で0.1動かせば一秒で6動くことになるでしょう。

しかし実際にはこの方法は使われなくて、
かわりに(やはり一秒に60回呼ばれる)Game.Updateメソッドの中でデータの変更を行います。

(※この理由はおそらくGUIアプリケーションで
ビューとモデルの分離を行う理由と同じでしょう。
データと、それの表示は分けておきたいものです。

「コンピュータサイエンスのいかなる問題も
間接層を付け加えることで解決できる」というわけです。
実際、UpdateメソッドはDrawメソッドとは
違う周期で呼ばれるように設定することも可能です。
Drawメソッドで何でもかんでもやっていたらそういうことは出来なかったでしょう。

まぁこういう小さいサンプルプログラムでは
Drawの中で何でもかんでもやったほうが簡単な気もしますが・・・)


コードはこうなります。
 

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    Texture2D texture;
    SpriteBatch spriteBatch;
    Vector2 position = new Vector2();

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

    protected override void LoadContent()
    {
        texture = Content.Load<Texture2D>("Content/Penguins");
        spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void Update(GameTime gameTime)
    {
        position.X += 0.5f;
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        spriteBatch.Begin();
        spriteBatch.Draw(texture, position, Color.White);
        spriteBatch.End();
    }
}


このプログラムでは、ペンギンを少しずつ右に移動させています。
xna4.0SimplestPenguinsMovingFirst.jpg
xna4.0SimplestPenguinsMovingSecond.jpg

 

拍手[1回]


かんたんXNA4.0 その6 3D三角形の表示

ここでは3Dの三角形を表示します。
ここから、物を立体的に表示できるようになります。

物を立体的にウィンドウに表示するためには、
3次元の座標データだけでは足りません。
物はどこからどのように眺めるかによって
見え方が変わる
からです。

そのため、視点、見ている位置、視野角などの情報が必要になります。
それらの情報から、3Dの座標データを2Dのウィンドウに表示できるように変換してやります。
この変換に使うのが、行列(マトリックス)です。

マトリックスというのは「データの変換の仕方を表すデータ」のことで、
物理学では例えばアインシュタインの特殊相対性理論の
「光速に近い速度を持った物体は縮み、時間はゆっくり進む」(ローレンツ変換)
というのを数式で表すのに使ったりします。
つまり、日常の、歪んでいないデータをマトリックスにかけて、
縮んだ、時間のゆっくり進むデータに変換しているということです。

XNAをやるのに特殊相対性理論など全く知らなくてかまいませんが、
どちらともデータの変換を行います。
XNAでは物体を縮ませる代わりに、3Dデータを
ウィンドウの2Dデータに変換するのにマトリックスを使います。
(他にも3Dモデルを回転させたり、平行移動させたり、拡大したりするのにも使います)

コードは次のようになります。
 

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

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

    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)    //カメラの上向きベクトル。(0, -1, 0)にすると画面が上下逆になる
            ),
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

    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,//vertexOffset
                1 //primitiveCount
                );
        }
    }
}


xna4.0SimplestTriangle3D.jpg

ここでやっているのは、BasicEffect.ViewプロパティとBasicEffect.Projectionプロパティの設定です。
このそれぞれに、3Dデータから2Dスクリーンデータへの変換の仕方を指定するのです。
何で2つのプロパティを設定しているのかというと、3Dから2Dへの変換は複雑なので
2回に変換を分けているというだけのことです(ですから原理上は1回にすることも出来ます)

public Matrix View { get; set; }

絶対的な座標データを視点(カメラ)に対する
相対的な座標系に変換するマトリックスを設定または取得します。

public Matrix Projection { get; set; }

「Viewプロパティのマトリックスで変換した3D座標データを
さらに2D画面に映す(プロジェクション)変換」
を表すマトリックスを設定または取得します。

ここにセットするマトリックスによって、
遠近法の効果がある映し方にしたり、
あるいはどんなに遠くになっても物が小さくならない映し方(図面を描くときにいいでしょう)
にすることにすることも出来ます。
ゲームを作るなら遠近法があったほうがいいですね。


さて、この変換を表すマトリックスを作るのには、別に難しい知識は必要なくて、
ただ単にMatrixクラスの生成メソッドを呼び出すだけでかまいません。
ここではMatrix.CreateLookAtメソッドと
Matrix.CreatePerspectiveFieldOfView(遠近法になります)メソッドを使って生成しています。
この2つに視点の位置や視野の広さなどを引数にしてマトリックスをつくり、
3Dデータを2Dに変換します。(実際に変換しているのはBasicEffect内部のHLSLですが)

カメラの向き

詳しく見ていきましょう。

public static Matrix CreateLookAt (
         Vector3 cameraPosition,
         Vector3 cameraTarget,
         Vector3 cameraUpVector
)


CreateLookAtメソッドは、最初の座標データをカメラに対する相対的な座標系に変換するマトリックスを作り出します。

cameraPosition視点(カメラ)の位置です。これを変えると物体を違う方向から眺めることが出来ます。

xna4.0SimplestTriangle3DWithCameraMoved.jpg
※cameraPositionが(2, 0, 3)のとき

cameraTargetカメラのターゲットの位置です。ここを見ます。

cameraUpVectorカメラの上方向のベクトルです。
普通は(0, 1, 0)です(y軸方向が上)が、
これを逆さま(0, -1, 0)にすると、画面も逆さまになります。
xna4.0SimplestTriangle3DWithCameraUpDirectionInverted.jpg

もちろんこれを横(1, 0, 0)にすると、画面も横に傾きます。

xna4.0SimplestTriangle3DWithCameraUpDirectionX.jpg



遠近法

次はMatrix.CreatePerspectiveFieldOfViewメソッドです。

public static Matrix CreatePerspectiveFieldOfView (
         float fieldOfView,
         float aspectRatio,
         float nearPlaneDistance,
         float farPlaneDistance
)

(MSDNより)

CreatePerspectiveFieldOfView.jpg
引数の解説図です。
ただし名前に省略が多いです。
(DirectX11の解説図の流用なのです。XnaはDirectXより命名が丁寧です)
fov = fieldOfView
aspect = aspectRatio
znear = nearPlaneDistance
zfar = farPlaneDistance


fieldOfView視野の角度です。人間が両目で見えるのは140°位
(実際はもっと広いのですが、目の端ではたいしたことはわかりません)
と言われていますが、そんなに広くしない方がいいでしょう。
広くすると迫力が出る一方、画面が歪んで3D酔いしやすくなるという意見があるからです。
このサンプルでは45°にしています。

なお、これは360が一回転の「度(°)」ではなくて
(6.28...)が一回転の「ラジアン」という単位を使います。
「ラジアンなんて使いたくない!」という方は
度→ラジアンへの変換を行うMathHelper.ToRadiansメソッドがありますので御安心を。

aspectRatioは表示する画面の四角形の形を表します。
具体的には
画面の横幅 / 縦幅
です。

nearPlaneDistancefarPlaneDistanceは一緒に解説した方がいいでしょう。
これらは、3Dモデルが表示されるカメラからの距離の範囲を表します。
逆に言うと、この2つにはさまれた領域の外では、物体は表示されません。
カメラに近すぎると表示されませんし、遠すぎても表示されなくなります。
この2つの引数は、その距離を表します。
このサンプルでは1と100に設定しています。
つまり、カメラとの距離が1以下のものは表示されず
100以上のものも表示されないということです。

 

拍手[1回]