忍者ブログ

Memeplexes

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

[PR]

×

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


かんたん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回]

PR

かんたん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回]


かんたんXNA4.0 その5 三角形の表示

ここまでは2Dでしたが、すこし3Dに近づきたいと思います
(といってもこの回はまだ2Dと変わりありませんが)

XNAの3Dでは(というかXNAに限ったことではないのですが)
物体を三角形の集合で表します。
ポリゴンですね。

たとえば、四角いドアを描こうと思えば、
2つの三角形を使います。
(それだけだと厚さのない薄っぺらいドアですが、
厚みを出そうと思えばその分6つの四角を描けばいいだけです)

丸いボールを描こうと思えば、ものすごく小さい三角形を
たくさん組み合わせて表します。

三角形の描画はCGの基本です。
というわけでまずは三角形を1つだけ表示してみましょう。
 

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
        };
    }

    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.0SimplestTriColorTriangle.jpg


このサンプルは三角形を1つだけ表示しています。
その座標は、
(1, 1)
(0, 0)
(-1, 0)
です。
(このとおり、座標はウィンドウの左上が(-1, 1)、右上が(1, 1)、
左下が(-1, -1)、右下が(1, -1)となっています。)


このサンプルでは色の付いた三角形ですが、
テクスチャの写った三角形を作ることも可能です。
(むしろ、実際にゲームを作るとなればそっちの方がメインになるでしょう。)
こういったことは頂点のデータで決めます。

ここで用いた頂点はVertexPositionColor構造体で、座標と色の2つのデータを持っています。
別の構造体を使えば、別の性質を持った三角形を描けるというわけです。
(例えば、VertexPositionTexture構造体を使えばテクスチャの写った三角形を作ることが出来ます。)

public struct VertexPositionColor

コンストラクタ:

public VertexPositionColor ( Vector3 position, Color color )

フィールド:

public Color Color
public Vector3 Position



さて、ここで
何で3次元の座標データを使ったのにやってることは2Dと変わらないんだ
と思うかもしれません。
その答えは、3次元のデータを扱えるだけの十分な情報を設定していないからです。
今回は詳しい説明はしませんが、3次元データを表示するには少なくとも視点が必要です。
その視点(とその他いろいろ)のデータを使って、
3次元データを2次元のディスプレイに表示できるデータに変換し、
ウィンドウに映し出す必要があるのです。
変換の仕方を設定しなければ立体的に表示できないのです。

ここで移っている3色の3角形は、データの変換の仕方が設定されていないため、
2次元のディスプレイに既に表示できるデータであると解釈されて、
そのまま表示されてしまったのです。

BasicEffect

ソースコードの詳しい部分を見ていきましょう。
目に付くのは、BasicEffectクラスです。
日本語に訳すと「基本的な効果」・・・で
何を言っているのかさっぱりわかりませんが、
これはつまり、データがどのように描画されるかを制御するクラスです。
これはかなり重要なクラスで、3Dで描画するときには常にこれを使うと考えていいでしょう。
(実際には自分でBasicEffectの代わりになるEffectを作ってもいいのですが、
これはとても大変で、下手をするとDirectX9の普通の使い方よりもはるかに難しくなります
どのくらい難しいかと言うと、HLSLという描画の方法を細かく指定するための言語を覚えて、
それでいちいち「どのピクセルがどんな色になるのか」といったことを指定しなければなりません。
これはサンプルには荷が重過ぎます。)


public class BasicEffect : Effect

例えば、次のようなものを設定します:
先ほどの3Dデータから2Dデータへの変換の仕方、
表示するときにライトは使うのか、使うとしたらどのようなライトを使うのか・・・
表示するときに霧の効果(奥に行けば行くほどぼやける)を使うのか、使うとしたらどんな霧を使うのか・・・
表示するポリゴンはテクスチャを使うのか、使うとしたらどんなテクスチャを使うのか・・・
・・・といったものを設定します。

おそらく「どのように描画されるか」をEffect(効果)と表現したのでしょう。
でも究極的にはプログラムはどんなものだって効果と言えるわけですから、
無意味なネーミングであるような気もします。

話を戻すと、このBasicEffectのインスタンスを作った後
(ちなみにコンストラクタの2番目の引数は
リソースを共有するグループを表すEffectPoolのインスタンスを指定しますが
、ここでは別に共有とか何とかをするわけではないのでnullです)

さっそくBasicEffect.VertexColorEnabledプロパティで
「表示するときに色を考慮に入れるか」を設定しています。

public bool  VertexColorEnabled { get; set; }

なお、これを設定しないと、次のように色が考慮されなくなってしまいます:
BasicEffectにはいろいろな描画するモードがあって、
そのモードをオンにしてやらないと上手く表示されないと言うわけです。
xnaSimplestTriangleWithoutColor.JPG(注をわざわざ入れる必要はないとは思いますが、全部真っ白です)




Drawメソッドの中を見てみましょう。

まず、Effect.CurrentTechniqueプロパティで現在の描画モードを取得し、
EffectTechnique.Passesプロパティで全てのエフェクトパスを取得しています。

public EffectTechnique CurrentTechnique { get; set; }

public EffectPassCollection Passes { get; }

エフェクトパス(Microsoft.Xna.Framework.Graphics.EffectPass)
というのはちょうどお絵かきソフトで言うレイヤーのようなもので、
いろんな描画の効果を別々に表したものです(たぶん)。
例えば、
描画したいものの大体の色を表すエフェクトパス、
物のハイライトを表すエフェクトパス、
周りの環境からの光の反射による色を表すエフェクトパス、
影を表すエフェクトパス、
その他いろいろでしょう(たぶん)。
Effectを自分で作るときにはこれはそこそこ重要になるかもしれません。
もっともこのプログラムでは、拍子抜けなことに、エフェクトパスは1つだけですが。
ですから、effect.CurrentTechnique.Passes[0]...などとしてもいいのです。
(ペイントでお絵かきをすることを連想させます)

そしてそれぞれのエフェクトパスについて、
EffectPass.Apply()メソッドを描画の前に呼んでいます
(お絵かきソフトでそれぞれのレイヤーを表示しているようなものです)

public void Apply()

この後に呼ばれているのが
今回最も重要なGraphicsDevice.DrawUserPrimitivesジェネリクスメソッドです。

今回はこのメソッドで3角形を実際に描画しています。

public void DrawUserPrimitives<T> (
         PrimitiveType primitiveType,
         T[] vertexData,
         int vertexOffset,
         int primitiveCount
) where T : ValueType


primitiveTypeは頂点データの役割を表します。
どういうことかというと、実はこのメソッドが描画できるのは三角形だけではなくて
点でも線でもいいのです。
ここにはどの図形を描画するかを指定するのです。

public enum PrimitiveType

メンバ
LineList lineList.JPG
LineStrip  lineStrips.JPG
TriangleList  triangleList.JPG
TriangleStrip  triangleStrips.JPG


vertexDataは頂点データです。
これは三角形の3つの頂点だったり、
線の始点と終点の集合だったりします。

vertexOffsetはオフセット、つまり引数の頂点データの使い始める場所です。
これはまず普通0でしょう。

primitiveCountは描画する三角形の数です。
vertexData.Lengthを3で割った値を入れるといいでしょう。
といっても実際は三角形以外も描画できるわけですから、
直線の数だったり(vertexData.Length / 2)
することもあります。



※なお、注意しておきたいことですが
この類のメソッド
(DrawUserPrimitives, DrawPrimitives, DrawUserIndexedPrimitives, DrawIndexedPrimitives)
はDrawメソッドの中で何回呼んでもかまいません!
私が3Dを勉強し始めたときには、
この類のメソッド(特にDrawPrimitivesとDrawIndexedPrimitives!)を一回しか呼んではいけないと勘違いしていて、
ばかげたことに、物を1つしか表示できない時期が続きました。

 

拍手[3回]


かんたんXNA4.0 その4 画像ファイルを表示

ここでは画像ファイルをウィンドウに表示します。

外部のファイルをあつかうには、ContentManagerを使います。
これは、画像ファイルや3Dモデル、フォントなどをゲームにロードするときに使います。
なお、このクラスを使うときには、XNA Game Studioの助けがないと地獄を見ます
というのも、それらのファイル一つ一つに対応するxnbファイルを作らなければならないからです。
(しかもこいつはバイナリファイルです)

例えば、image.jpgというファイルを直接使うことはできません。
"image.xnb"というファイルを"image.jpg"から作って、それを使わなければならないのです。

XNA Game Studioはそれを自動で行ってくれます。
このことを全く気にする必要はありません。
ただ"image.jpg"ファイルをソリューションエクスプローラーのコンテントにペーストするだけでいいのです。

以下がXNA Game Studioでビルドしたコードです。
(Mainメソッドは別のクラスに移動しました)
 

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Texture2D texture;

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

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

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        spriteBatch.Begin();
        spriteBatch.Draw(texture, new Rectangle(0, 0, 200, 150), Color.White);
        spriteBatch.End();
    }
}


xna4.0SimplestTexturePenguins.jpg

このプログラムは、
"Penguins.jpg"(から作られた"Penguins.xnb")
をゲームにロードして表示しています。

Loadジェネリックメソッドを使って"Penguins.jpg"をロードしているのです。
(正確にはそれから作られたxnbファイルですが)

 

拍手[0回]


かんたんXNA4.0 その3 2D画像の表示

前回はウィンドウの背景を塗りつぶしました。
しかしこれだけでは華がありません。
もう少しましにしましょう。

今回は1×1の画像(Texture2D)を作り、それを表示することにします。
黒い四角を表示するのです。

まず、2Dの描画を行うには、2D描画を全てカプセル化しているSpriteBatchクラスを使います。
このクラスはTexture2Dを表示するSpriteBatch.Drawメソッドや、ここでは扱いませんが、文字列を表示するDrawStringメソッドを持っています。

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Texture2D texture;

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

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        texture = new Texture2D(GraphicsDevice, 1, 1);
        texture.SetData<Color>(new Color[] { Color.Black });
    }

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

    static void Main()
    {
        using (MyGame game = new MyGame())
        {
            game.Run();
        }
    }
}

xna4.0SimplestTexture.jpg 
※左上に、黒い1x1のTexture2Dを、 100x100に拡大して描画しています。

前回と比べて新しく出てきたのが、LoadContentメソッドです。
これは、グラフィックスを表すデータ(例えば今回のようにTexture2Dだったり、あるいは3Dのモデルだったりです)をロードすべきときに自動的に呼ばれます。
このメソッドの中で、SpriteBatchのコンストラクタを呼んでいます。
引数にGraphicsDeviceがあるのは、SpriteBatchがGraphicsDeviceを カプセル化しているからでしょう。(多分)

その後Texture2Dの1×1インスタンスをを作成しています。
その後、その1×1のマスを黒で埋めています。
1×1の黒いTexture2Dができあがったというわけです。
もちろん、サイズを変えてもうちょっとカラフルにすることもできます。

        texture = new Texture2D(GraphicsDevice, 3, 1);
        texture.SetData<Color>(new Color[] { Color.Navy, Color.White, Color.Red });

xna4.0SimplestTextureTriColor.jpg

MyGame.Drawメソッドに目を転じましょう。
背景をCornflowerBlueで塗りつぶした後、 左上に100×100に拡大したTexture2Dを描画しています。
そのとき、頭と後ろにSpriteBatch.Beginメソッドと SpriteBatch.Endメソッドを呼んでやる必要があります。
呼ばなければ例外が発生し強制終了です。
これは、2D画像を表示するときだけではなくて、文字を表示するときも同じです。
ちなみに、もちろん、BeginとEndの間にDrawメソッドは何回呼んでもかまいません。


        spriteBatch.Begin();
        spriteBatch.Draw(texture, new Rectangle(0, 0, 100, 100), Color.White);
        spriteBatch.Draw(texture, new Rectangle(200, 200, 80, 80), Color.White);
        spriteBatch.End();

xna4.0SimplestTextureTwoRectangle.jpg
(※2回呼べばとうぜん2つの四角が表示されます)

ちなみにこのColor.Whiteというのは描かれるテクスチャに色付けされる色のことです。
ここに指定する色が青ならばテクスチャは青みがかって描かれます。

さて、今回のこの方法はあまりスマートではありません。
画像はなるべく外部のファイルにしておきたいものです。
次回はその方法について扱います。

 

拍手[2回]