忍者ブログ

Memeplexes

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

[PR]

×

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


かんたんXNA4.0 その20 深度バッファ

3Dグラフィックスでは、奥にある物体は手前にある物体に
隠されなければなりません。

例えば、壁の向こうに敵がいる場合、敵は壁に隠れて見えません。
(当たり前です!)

XNAではこのことを特に気にする必要はありません。
ふつうに描画するだけで自動的に奥のものが隠れてくれます。

depthBufferEnabled.jpg
(奥に赤い三角形、手前に青い三角形があります。
奥の三角形は手前の三角形に隠れています。)


これは内部で深度バッファというものを使っているからです。
深度バッファというのは色のデータと共に各ピクセルごとに
存在するデータのことで、
今一番手前の物体のカメラからの距離を表しています。
(もっともこれは相対的な値で、0.0から1.0までです)

もし今描画しようとしている物体の距離が深度バッファのものより遠ければ
(つまり今描画しようとしている物体が隠れてしまうのなら)
描画しないことにしています。
もし今から描画する方が近いのなら、描画して、
深度バッファを今描いたものに更新します。

このように深度バッファを使うことによって適切な描画が出来るのですが、
特殊な効果を狙ってあえて深度バッファを使わないようにすることが可能です。

これにはGraphicsDevice.DepthStencilStateプロパティを使って設定します。

public DepthStencilState DepthStancilState { get; set; }

これを使うと、深度バッファを有効にしたり無効にしたり出来ます。
デフォルトはもちろん有効です。

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


class Triangle
{
    VertexPositionColor[] vertices = new VertexPositionColor[3];

    public Triangle(Vector3 p1, Vector3 p2, Vector3 p3, Color color)
    {
        vertices[0] = new VertexPositionColor(p1, color);
        vertices[1] = new VertexPositionColor(p2, color);
        vertices[2] = new VertexPositionColor(p3, color);
    }

    public void Draw(GraphicsDevice graphicsDevice, BasicEffect effect)
    {
        effect.VertexColorEnabled = true;

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

            graphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );
        }
    }
}

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices;

    Triangle blueTriangle = new Triangle
    (
        new Vector3(0, 1, 0),
        new Vector3(1, -0.5f, 0),
        new Vector3(-1, -0.5f, 0),
        Color.Blue
    );
    Triangle redTriangle = new Triangle
    (
        new Vector3(1, 0.5f, -1),
        new Vector3(0, -1, -1),
        new Vector3(-1, 0.5f, -1),
        Color.Red
    );

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

    }

    Vector3 getPosition(int index, int count)
    {
        return new Vector3(
            (float)Math.Sin(2 * Math.PI * index / count),
            0,
            (float)Math.Cos(2 * Math.PI * index / count)
            );
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            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);

        GraphicsDevice.DepthStencilState = DepthStencilState.None;

        blueTriangle.Draw(GraphicsDevice, effect);
        redTriangle.Draw(GraphicsDevice, effect);
    }
}


depthBufferDisabled.jpg
ここでは、2つの三角形を描画しています。
手前にあるのが青い三角形
奥にあるのが赤い三角形です。

しかし、深度バッファを無効にしたため、
隠れて見えないはずの赤い三角形が
青い三角形の上に描画されています。

まず青い三角形を描画し、その後赤い三角形を描画したため、
赤い三角形で上書きされてしまうのです。
(もし深度バッファが有効なら、赤い三角形を後から描いてもきちんと隠れてくれます!)


 

拍手[0回]

PR

かんたんXNA4.0 その19 マテリアル

物の色はライトの色だけで決まるわけではありません。
真っ黒いものは赤い光が当たろうと緑の光が当たろうと
青い光が当たろうと白い光が当たろうと
黒です。

緑の葉っぱに白い光を当てると、
反射する光は白ではなく緑です。

どういうことかというと、
物の見かけの色はライトの色だけでなく、物そのものの色にも影響される
といういことです。
物体にどんな光が当たるかということだけではなく、
それがどれだけ物体によって跳ね返されるかにも影響されるのです。

この「物そのものの色」をマテリアルといい、
BasicEffectのプロパティで設定します。

public Vector3 DiffuseColor { get; set; }

DirectionalLightのDiffuseColorをどれだけ跳ね返すかを表します。

public Vector3 SpecularColor {get; set; }

DirectionalLightのSpecularColorをどれだけ跳ね返すかを表します。

public float SpecularPower { get; set; }

SpecularColorの色の収束っぷりを表します。
大きいほどよりよく収束します。
specularPower1.JPGSpecularPower = 1
specularPower10.JPGSpecularPower = 10
specularPower100.JPGSpecularPower = 100

public Vector3 AmbientLightColor { get; set; }

環境光による色を表します。
周りで複雑な反射を繰り返し、最終的に物体に当たる光による色です。
これは物体やカメラがどんな位置や角度にあろうと必ず物体の色に反映されます。

public Vector3 EmissiveColor { get; set; }

自分自身が放つ色です。
他のプロパティが光の反射による、惑星の光だとすれば、
このプロパティは自分から光を発する恒星の光といったところでしょうか。

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices;

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

        int planeCount = 30;
        vertices = new VertexPositionNormalTexture[planeCount * 6];

        for (int i = 0; i < planeCount; i++)
        {
            VertexPositionNormalTexture[] plane
                = new VertexPositionNormalTexture[4];

            plane[0] = new VertexPositionNormalTexture(
                    getPosition(i, planeCount) + new Vector3(0, 1, 0),
                    getPosition(i, planeCount),
                    new Vector2((float)i / planeCount, 0)
                );
            plane[1] = new VertexPositionNormalTexture(
                    getPosition(i + 1, planeCount) + new Vector3(0, 1, 0),
                    getPosition(i + 1, planeCount),
                    new Vector2((float)(i + 1) / planeCount, 0)
                );
            plane[2] = new VertexPositionNormalTexture(
                    getPosition(i, planeCount) + new Vector3(0, -1, 0),
                    getPosition(i, planeCount),
                    new Vector2((float)i / planeCount, 1)
                );
            plane[3] = new VertexPositionNormalTexture(
                    getPosition(i + 1, planeCount) + new Vector3(0, -1, 0),
                    getPosition(i + 1, planeCount),
                    new Vector2((float)(i + 1) / planeCount, 1)
                );

            vertices[i * 6] = plane[0];
            vertices[i * 6 + 1] = plane[1];
            vertices[i * 6 + 2] = plane[2];

            vertices[i * 6 + 3] = plane[1];
            vertices[i * 6 + 4] = plane[3];
            vertices[i * 6 + 5] = plane[2];
        }
    }

    Vector3 getPosition(int index, int count)
    {
        return new Vector3(
            (float)Math.Sin(2 * Math.PI * index / count),
            0,
            (float)Math.Cos(2 * Math.PI * index / count)
            );
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            LightingEnabled = true,
            View = Matrix.CreateLookAt
            (
                new Vector3(0, 0, 8),  //カメラの位置
                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);

        effect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
        effect.DirectionalLight0.DiffuseColor = Color.Gray.ToVector3();
        effect.DirectionalLight0.SpecularColor = Color.White.ToVector3();

        effect.DiffuseColor = Color.Green.ToVector3();
        effect.SpecularColor = Color.Blue.ToVector3();
        effect.SpecularPower = 100;

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

            GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,//vertexOffset
                vertices.Length / 3 //primitiveCount
                );
        }
    }
}

material.JPG

このプログラムは、円筒形を表示して、そのマテリアルを設定しています。
マテリアルのDiffuseColorは緑なので、ライトの緑成分しか跳ね返しません。
(つまりライトに緑成分がなければ真っ黒に見えます。
lightDiffuseNoGreen.JPGライトのDiffuseColor = (0.5f, 0, 0.5f)


一方マテリアルのSpecularColorは青なので、
円筒のハイライト部分は青くなっています(不自然なことに!)

自然な感じにしたいのならマテリアルの
SpecularColorは白にしたほうがいいでしょうね。
specularWhite.JPG

拍手[0回]


かんたんXNA4.0 その18 BasicEffectで平行光

BasicEffect.EnableDefaultLightingメソッドを使うと
簡単なライティングを行うことが出来ますが、
BasicEffect.DirectionalLight0プロパティを使うと、
もう少し詳しいライティングの設定が出来ます。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices = new[]
    { 
        new VertexPositionNormalTexture
        {
            Position = new Vector3(0, 1, 0),
            Normal = new Vector3(0, 0, 1),
        },
        new VertexPositionNormalTexture
        {
            Position = new Vector3(1, 0, 0),
            Normal = new Vector3(0, 0, 1),
        },
        new VertexPositionNormalTexture
        {
            Position = new Vector3(-1, 0, 0),
            Normal = new Vector3(0, 0, 1),
        },
    };

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            LightingEnabled = 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);

        effect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
        effect.DirectionalLight0.DiffuseColor = Color.Gray.ToVector3();
        effect.DirectionalLight0.SpecularColor = Color.White.ToVector3();

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

            GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,//vertexOffset
                1 //primitiveCount
                );
        }
    }
}
xna4.0SimplestDirectionalLight.jpg
このサンプルプログラムでは、斜め45度の角度から三角形に光を照射しています。
左が明るく、右が暗くなっています。
ここでは、太陽光のように光源が十分に遠くにある、光をシミュレートしています。
(そのため、指定するのは光の方向だけで、光源の位置は指定しません
「十分に遠くにある」という情報だけで十分なのです。
十分に遠くにあれば光の方向は大体どこでも同じだからです。
もし光源が近くにあるのなら、
光の方向はちょっと動いただけでも変わりますが、
遠くにあるのならほとんど変わりません。
ちょうど車で移動しているときに月がどこまでも
追いかけてくるように見えるのと同じ理由です。)


まず、BasicEffectで(手作業で)ライティングを行いたいときには、
BasicEffect.LightingEnabledプロパティをtrueにセットします。
(EnableDefaultLightingメソッドを使うときにはこの作業がいらなかったのは、
多分メソッドの内部でこのプロパティをtrueにセットしてくれているからでしょう。)


public bool LightingEnabled { get; set; }

これをtrueにセットしないとライティングが考慮されなくなって
普通に描画されてしまいます。(影がつきません)
xna4.0SimplestLightingDisabled.jpg

そして、無限遠から来る光、平行光を使うには
BasicEffect.DirectionalLight0プロパティを使います。

public BasicDirectionalLight DirectionalLight0 { get; }

一番最後に"0"がついているのは、平行光を複数使いたいときのために、
まだまだ他にも用意されているからです。
(ここで使っているのは最初の0だけですが)

public BasicDirectionalLight DirectionalLight1 { get; }
public BasicDirectionalLight DirectionalLight2 { get; }


BasicDirectionalLightを使うにはまずEnabledプロパティをtrueにセットします。

その後、BasicDirectionalLight.Directionプロパティで光の方向を決めます。

public Vector3 Direction { get; set; }

光の色を決めるのがBasicDirectionalLight.DiffuseColorプロパティと
BasicDirectionalLight.SpecularColorプロパティです。
(この2つは色を表すプロパティですが、
型がColorではなくVector3になっています。
これはBasicEffectが実はきたならしいHLSLのラッパーで
あることと関係があるのでしょう。)


public Vector3 DiffuseColor { get; set; }
public Vector3 SpecularColor { get; set; }


どうして光の色を決めるプロパティが2つもあるのかというと、
この2つは性質が違うからです。
DiffuseColorは乱反射による色ですが、
SpecularColorは鏡面反射による色です。
この2つを合成した色が実際には表示されます。

この違いをはっきりさせるには、片方だけセットしてみるといいでしょう。

乱反射(Diffuse)

まず、DiffuseColor(乱反射)だけセットするとこうなります。
directionalLightDiffuse.JPG

全体的に暗めでのっぺりしています。
どこも同じような明るさです。
この明るさは面に当たる光の角度に依存しています。

diffuse90.JPG90度(三角形に平行:横からの光)
diffuse75.JPG75度
diffuse60.JPG60度
diffuse30.JPG30度
diffuse15.JPG15度
diffuse0.JPG0度(三角形に垂直:まっすぐな光)

面に垂直に光が当たるようになるほど明るくなるのがわかると思います。
これは赤道直下が暑くて北極や南極が寒くなる理由と同じです。(赤道は緯度0度で、北極点は北緯90度です。)

鏡面反射(Specular)

次に、SpecularColor(鏡面反射)だけセットするとこうなります。

directionalLightSpecular.JPG

左側がぼんやりと明るく、右側は暗くなっています。
この明るくなっている部分は鏡に映った太陽とでも思えばいいでしょう。

実際、三角形を動かしていくとこうなります。
directionalLightSpecularMoved3.JPG
directionalLightSpecularMoved2.JPG
directionalLightSpecularMoved1.JPG



directionalLightSpecularMoved0.5f.JPG
directionalLightSpecular.JPG

光の方向と面とカメラの位置によって明るさが変わります。
カメラの位置によって明るさが変わるというのは乱反射ではありえないことです。
こういうのは鏡面反射でのみおこります。
ちょうどメガネがキラリと光るようなものです。

最終的には、この2つの効果が組み合わさります。
そうすることによって、そこそこ自然な描画が出来るのです。
環境光(Ambient Color)

このサンプルでは使いませんでしたが、環境光というものもあります。
これは、光がどのような角度で当たっても、
カメラがどのような方向から見ても、
同じような明るさになります。

どういういうことかというと、環境光とは周囲を反射しまくった微妙な光のことなのですが、
それをわざわざ計算してシミュレートするのはとても莫大な計算が必要になるため、
とりあえず「どんなときにも当たる光」ということにしているのです。

これを表すプロパティは、BasicEffect.AmbientColorです。

public Vector3 AmbientLightColor { get; set; }

拍手[1回]


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


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