忍者ブログ

Memeplexes

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

[PR]

×

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


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

PR

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


かんたんXNA4.0 その12 Game.Initializeメソッド

小さなサンプルではその有効性はさっぱりわかりませんが、
大きなプログラムではGame.Initializeメソッドが
(グラフィックスで無い)データの初期化を分離するのに役に立ちます。
(これはちょうどjavaアプレットのinitメソッドのようなものです)

例えば座標データをこの中で初期化することが出来ます。

 

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionColor[] vertices;
    VertexBuffer vertexBuffer;

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

    protected override void Initialize()
    {
        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),
        };
        base.Initialize();
    }

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

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

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

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

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,  //startIndex
                vertices.Length / 3 //primitiveCount
                );
        }
    }
}
xna4.0SimplestInitialize.jpg
(※前回と見た目は全く変わりません。)

ここでは、今までコンストラクタ内で初期化していた三角形の頂点データを、
MyGame.Initializeメソッド内で初期化しています。

そのあとbase.InitializeでGame.Initializeメソッドを呼んでいます。

不可解です
なぜスーパークラスのInitializeメソッドを呼ばなければならないのでしょう?
その理由はもちろんWindowsFormsのControl.OnPaintメソッドの
オーバーライドでスーパークラスのOnPaintメソッドを
呼んでやらなければならない理由と同じです。
つまり、Game.Initializeメソッドが何か大切なことをしているからです。

このケースでは、Game.InitializeメソッドはLoadContentメソッドを呼んでおり、
base.Initialize
を忘れるとグラフィックス関係の変数が全て初期化されずにnullのままで、
描画すると同時に例外(NullReferenceException)が飛びます。
(同様に、DrawメソッドとUpdateメソッドも本当はスーパークラスの
メソッドを呼んでやらなければなりません。
しかしGameComponentを使わない限りは大して問題はないですし
サンプルなので割愛しました。
実際に問題が出る時に対処しても遅くは無いでしょう。
YAGNIの原則です。)


これはまぁいいでしょう。
しかしなぜ一番最後にbase.Initializeを呼んでいるのでしょうか?

コンストラクタは普通基底クラスのものから先に呼ばれます。
派生して出来たクラスのコンストラクタで基底クラスの何かを
利用するときに困らないようになっているのです。
とすると同じ初期化を表すInitializeメソッドも一番最初に
base.Initializeを呼ぶべきであるようにも思えます。
一番最後に呼ぶのではまるでデストラクタです。

一番最後にbase.Initializeとするのは
気持ち悪いように思えます。
なぜ一番最後に呼ぶのでしょう?

まず一番最初に呼ぶとどうなるかを実際に見るのがいいかもしれません。
…例外がスローされます。
verticesがまだnullだというのです。

実はこれは一つ目の疑問と関係しています。
Game.InitializeメソッドはLoadContentを呼んでいるので、
一番最初にGame.Initializeメソッドを呼ぶと
まだ三角形の頂点データが初期化されていないまま
VertexBufferが初期化されてしまうのです。

したがってVertexBufferのnew時に
例外がスローされるわけです。
 

拍手[0回]


かんたんXNA4.0 その11 VertexBuffer

ここまで三角形や四角形を描くのに
GraphicsDevice.DrawUserPrimitives<T>メソッドを使ってきました。
これはシンプルではあるのですがパフォーマンスがあまりよくありません。

良いパフォーマンスを持っているのは
GraphicsDevice.DrawPrimitivesメソッドです。
ただし、これは頂点データの配列そのものは直接使えません。
配列からVertexBufferを作り、それを使わねばならないのです。
 

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),
    };
    VertexBuffer vertexBuffer;

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

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

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

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

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,  //startIndex
                vertices.Length / 3 //primitiveCount
                );
        }
    }
}

 

xna4.0SimplestVertexBuffer.jpg

このサンプルではGraphicsDevice.DrawUserPrimitivesのかわりに
GraphicsDevice.DrawPrimitivesメソッドを
使って三角形を描画しています。
見た目は変わりません。
ただ、ほんの少しパフォーマンスはよくなっているはずです。
(サンプルではありがたみは全く感じられませんが)

このメソッドを呼ぶ前にはSetVertexBufferでvertexBufferを
セットしてやらなければなりません。
DrawPrimitivesはVertexBufferが引数に無いのです。


さて、ここで注意しておきたいことはこのSetVertexBufferとDrawPrimitivesの
ペアは一回のDrawメソッドの中で何度呼んでもいいということです。
(そうでなければ物体を2つ以上描画できなくなってしまいます。)

物体を2つ以上描画するときはこんな感じでいいでしょう。

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

            GraphicsDevice.SetVertexBuffer(vertexBuffer1);
            GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,  //startIndex
                vertices1.Length / 3 //primitiveCount
                );
        }

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

            GraphicsDevice.SetVertexBuffer(vertexBuffer2);
            GraphicsDevice.DrawPrimitives(
                PrimitiveType.TriangleList,
                0,  //startIndex
                vertices2.Length / 3 //primitiveCount
                );
        }










拍手[1回]