忍者ブログ

Memeplexes

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

[PR]

×

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


3D酔いの原因

3D酔いとは?

3Dゲームをしていると乗り物酔いのように気分が悪くなり吐き気を催すことがあります。
これを3D酔いといいます。

例えばマイクロソフトのシューティングゲーム、Halo(体験版はこちら)は
面白いゲームですが3D酔いでその面白さが半分台無しになっています。

ワートホグ(車のこと)に乗れば5分も経たずに気分が悪くなってきます。
視点が揺さぶられるわカメラが障害物をすいすい(この「すいすい」がいけません!)避けるわで
すぐにゲームを続けたい気持ちとさっさとトイレに
駆け込みたくなる気持ちの間でアンビバレンツに陥ります。
(まぁしばらくすると落ち着くのですが)
3D酔いの原因

この3D酔いの詳しい原因はよくわかっていませんが、
大体の原因は目から入る情報と三半規管から入る情報のミスマッチであることは間違いないようです。
(こんなことがわかっても3D酔い対策はほとんど出来ないのであまり意味の無い話です。)

つまり、目からは「自分は動いている」と言う情報が来るのに
三半規管からは「自分は止まっている」と言う情報が来るので
脳が混乱して気分が悪くなったり、吐き気がするというわけです。

これはちょうど乗り物酔いの逆で、乗り物酔いの場合は
目からは「自分は止まっている」という情報が来るのに
三半規管からは「自分は動いている」と言う情報が来るわけです。
(ですから乗り物酔いになったときは外の風景を眺めてこの情報の食い違いを解消するのです。)
しかしこれは「情報のミスマッチ」が酔いを引き起こすと言う点で同じです。

また似たような症状として「宇宙酔い」があります。
宇宙酔いというのは宇宙の無重力空間でやはり気分が悪くなり吐き気を催してしまう症状のことで、
現在スペースシャトルの乗組員のだいたい60%が最初のフライトで宇宙酔いに悩まされます。
この原因はおそらく重力が打ち消されているからで、
実際宇宙飛行士が特に吐き気に襲われるのは地上の、
重力のある環境ではありえないものを見たときです。
(例えば同僚の宇宙飛行士が頭をにしていたりとか)
これもやはり目と三半規管のミスマッチが引き起こすものです。

このことから考えて、乗り物酔いにしろ3D酔いにしろ宇宙酔いにしろ全て
目と三半規管から脳に入ってくる情報のミスマッチ」が原因であると考えてよさそうです。



ダーウィン主義的理由

情報のミスマッチがなぜ吐き気をもたらすのでしょう?

一般には単に「それは脳が混乱するからだ」と言われますが、
オックスフォード大学の心理学者のマイケル・トリーズマンは
驚くべき、しかし説得力を持った別の説を提唱しています。

その説によると、「脳はわざと気分が悪くなり吐こうとしている」のです。
一見ありえなさそうに思えますがそうでもありません。
風邪を引いたときに高い熱が出るのは体内の病原体を殺すためです。
同様に、乗り物酔いになり、吐き気がするのにもきちんとした理由があるという理屈です。

この説は次のように進みます。
動物が何かを吐くのは、毒があるものを食べてしまったときに体からそれを排除するためです。
吐き気と言うのは、毒を食べてしまったときにその個体を操って毒を排除するためのコマンドだと言うわけです。
・・・そして毒と言うのは、たいてい神経に働き、神経系を狂わせます。
ということは逆に言って、そのコマンドを発動すべきタイミングは
神経系が何かおかしくなったときに違いありません。

しかし、「何かおかしくなったとき」とは具体的にどう定義すればいいのでしょう?
自然淘汰は基本的にXPでいう仮実装しかしないやっつけプログラマーなのでこう「考え」ます:
「脳のいろんな部分の重力に関する意見を聞けばいい。
重力が無くなったり変わったりすることなんてありえないからいい指標になる。
それでどいつかの意見が食い違っていたらこれはきっと神経系がやられてる。」
よし!これでとりあえず動くでしょう。

かくして、脳の内部に存在している重力(というか加速度)をあつかう
モジュールのいずれかの間で情報の食い違いが発生すると
神経系が毒で狂っていると認識し、「吐き気コマンド」を発行することになりました。

さて、そのような生存機械がもし車の後部座席に乗って本を読んだり、
Haloをプレイしたり、スペースシャトルに乗って無重量状態を体験したらどうなるでしょう?

これがこの説のエッセンスです。
酔いというのは、限られた環境では上手く動く防衛機能が暴走するすることによって起こる、
花粉症のようなものだったのです。

教訓

このことから何が言えるでしょうか。
具体的には、3D酔いを防ぐために何が出来るでしょう?

理論上、神経系がおかしくなったと認識されなければ3D酔いにはならないはずです。
しかしそうするには目から入る情報と三半規管から入る情報を一致させなければなりません。
(いや、目と三半規管に限定するのは危険かもしれません。
脳の中でミスマッチを生むものならなんだっていいはずです)


かといって完全に一致させることなど不可能です。
ゲームをプレイする人は普通いすの上に座って静止しています。
画面が動けば即アウトです。
そんなゲームはちっとも楽しくありません。

もっとも実際にはそんなに厳しくなくて、ある程度動きは許容されるはずです。
というのも自然状態では脳のモジュール間のある程度の誤差はつきものだからです。
少し違ったくらいで毎回吐いていたら体力が持ちません。逆効果です。
おそらく誤差がある閾値をこえて初めて「吐き気コマンド」が発動するのでしょう。

ということはゲームの画面をある程度「止まっている」ように見せればいいはずです。
ある程度、画面を非現実的にして、脳がその情報をあまり信じないようにすればいいかもしれません!
ゲーム画面の視野の角度を小さくして臨場感をあえて抑えると言うのが手です。
いやでも角度が小さすぎてもそれが何らかのミスマッチを生み出し逆効果になるかもしれませんね・・・。
(そして、理論を弄って出る結論としてよくあることですが、こんなことは最初からわかっています!)

カメラをごちゃごちゃ動かさないというのもいいかもしれません。
あんまり動かしすぎると脳内のミスマッチが大きくなる可能性があります。

あるいは逆にカメラワークを上手くすることによって
脳が画面からの情報を重視しないように出来るかもしれません。
もちろんその条件はわかりませんし、第一こんなことが出来るかどうかもわかりませんが。

ソフトウェアからできるのはこのくらいが限界でしょう。
しかしハードウェアを使ってもいいのなら、動く椅子なり
三半規管に電極をつないで動いているような信号を出力させるなり
方法はいくらでもあるはずです。

つまり結論は、「人類はさっさと電脳を実用化させて3D酔いを克服すべきだ」と言うことですね!


 


拍手[0回]

PR

かんたんXNA その17 ポイントスプライト

このページは古いです
最新版はこちら

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

これはパーティクル(粒子)をいっぱい表示することでそれらを表現する方法です。

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

それぞれの小さな炎(や煙)は位置と大きさの情報を持っており、
向きの情報は持っていません。
(これらの情報は、実際には頂点ではありませんが、頂点データとして扱います)
ポイントスプライトは常にカメラの方向を向くからです。

頂点データをある方法で描画してやるとポイントスプライトになります。

いろいろな方法がありますが、例えば前回出てきた
GraphicsDevice.RenderStateのFillModeプロパティを
FillMode.Pointにしてやるだけで簡単なポイントスプライトになります。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[3];

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

        vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.White);
        vertices[1] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Red);
        vertices[2] = new VertexPositionColor(new Vector3(-1, 0, 0), Color.Navy);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            effect.VertexColorEnabled = true;
        }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.FillMode = FillMode.Point;
        graphics.GraphicsDevice.RenderState.PointSize = 20;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );

            pass.End();
        }

        effect.End();
    }
}

simplestPointSprite.JPG
このプログラムは三角形を描画する代わりに、
大きさ20のポイントスプライトを3つ描画しています。

ポイントスプライトの大きさはRenderState.PointSizeプロパティで設定しています。

ただ、この方法はあまりスマートではなかったかもしれません。
というのもこれは結局「三角形の描画の代わり」なので
3の倍数(3、6、9、12、・・・)だけしかポイントスプライトを
表示できないからです。

煙を表示しようとお微妙な数の
ポイントスプライトを描画できるべきです。

まっとうなやり方は、
GraphicsDevice.DrawUserPrimitivesの引数
primitiveTypeをPrimitiveType.PointListに書き換えてやることです。

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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[3];

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

        vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.White);
        vertices[1] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Red);
        vertices[2] = new VertexPositionColor(new Vector3(-1, 0, 0), Color.Navy);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            effect.VertexColorEnabled = true;
        }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.PointSize = 20;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.PointList,
                vertices,
                0,
                3
                );

            pass.End();
        }

        effect.End();
    }
}

これも上と同じ表示結果になります。
違うのはソースコードの中身で、これは頂点データをポイントのリストとして表示しています。
(そのため引数primitiveCountは1ではなく3です。)
テクスチャを貼る

さて、ここまでは色付きの点しか表示していませんでした。
しかし現実にはそんな地味なものでゲームは作れません。
最低でもXNA Creators Clubのサンプルのように、
テクスチャ付きのポイントスプライトでなければなりません。

どんなテクスチャを貼るかは問題ですが、ここでは
Photoshopの「ぼかし(放射状)」を何度も繰り返して
やっつけた画像を使ってみます。
(別にペイントででっちあげてもかまわなかったような気もしますが)
blueFire.jpg(ちなみに、背景が黒なのは後で透過処理をするときにそこが透明であると解釈されるからです)

なお、この画像をポイントスプライトに表示させるのにするべきことは、
テクスチャをオンにしてセットすることくらいで特別なことはいりません。
それでそれぞれのポイントスプライトに上の青いぼやけた炎が描かれます。

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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[3];

    ContentManager content;
    Texture2D texture;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.White);
        vertices[1] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Red);
        vertices[2] = new VertexPositionColor(new Vector3(-1, 0, 0), Color.Navy);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            effect.VertexColorEnabled = true;

            texture = content.Load<Texture2D>("blueFire");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;
        graphics.GraphicsDevice.RenderState.PointSize = 100;
        
        effect.Texture = texture;
        effect.TextureEnabled = true;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.PointList,
                vertices,
                0,
                3
                );

            pass.End();
        }

        effect.End();
    }
}

pointSpriteWithTexture.JPG※そのままでは見にくいのでサイズを100にしました
3つのポイントスプライトに画像が表示されたと思います。
それぞれのポイントスプライトの色は違うため、それぞれ違った感じに描かれているはずです。
まともに描画されているのは白だけですね。

なお、注意すべきなのはRenderState.PointSpriteEnableプロパティ
をtrueにセットしなければならないことです。
名前からして前のサンプルからtrueにしなければならなかったようにも思えますが、
実際に問題が出るのはこのサンプルからです。

これはなぜかというと、このプロパティが制御するのは
ポイントスプライトにテクスチャがマップされるかどうか
ということだからです。

これをtrueにしなければ全てのポイントスプライトが、
テクスチャが表示されずに、真っ黒になってしまいます。
pointSpriteFailed.JPG
透過

さて、これでややましになってきましたが、まだ使い物になりません。
テクスチャに黒い縁がついているからです。
このようなポイントスプライトをどんなにならべても絶対に炎には見えないでしょう。

実用レベルにするには黒い部分を透過することが必要です。

透過を行うにはRenderState.AlphaBlendEnableプロパティをtrueにします。
そしてそれだけではまだダメで、さらに
RenderState.SourceBlend
RenderState.DestinationBlend
といったプロパティをセットして、どのような透過を行うのかを指定する必要があります。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System;


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[20];

    ContentManager content;
    Texture2D texture;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i] = new VertexPositionColor(
                new Vector3(
                    (float)Math.Cos(2 * Math.PI * i/vertices.Length),
                    0,
                    (float)Math.Sin(2 * Math.PI * i / vertices.Length)),
                Color.White
                );
        }
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 3, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );

            texture = content.Load<Texture2D>("blueFire");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
        graphics.GraphicsDevice.RenderState.SourceBlend = Blend.One;
        graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.One;

        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;
        graphics.GraphicsDevice.RenderState.PointSize = 100;
        
        effect.Texture = texture;
        effect.TextureEnabled = true;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.PointList,
                vertices,
                0,
                vertices.Length
                );

            pass.End();
        }

        effect.End();

        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
    }
}


pointSpriteCircle.JPG
ここでは、20個のポイントスプライトで円を作り、透過して描画してあります。
ゲームでミニラの放射熱線を描画したいのなら
こんな感じにするといいかもしれません。

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

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

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


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

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

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

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

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

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

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

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

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


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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[20];

    ContentManager content;
    Texture2D texture;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i] = new VertexPositionColor(
                new Vector3(
                    (float)Math.Cos(2 * Math.PI * i/vertices.Length),
                    0,
                    (float)Math.Sin(2 * Math.PI * i / vertices.Length)),
                Color.White
                );
        }
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 3, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );

            texture = content.Load<Texture2D>("blueFire");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
        graphics.GraphicsDevice.RenderState.SourceBlend = Blend.One;
        graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;
        graphics.GraphicsDevice.RenderState.PointSize = 100;
        
        effect.Texture = texture;
        effect.TextureEnabled = true;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.PointList,
                vertices,
                0,
                vertices.Length
                );

            pass.End();
        }

        effect.End();

        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
    }
}

pointSpriteCircleWithoutDepthBufferWriting.JPG
上手くいきました!

こうしてみるとミニラの放射熱線というよりもガスコンロの炎に似ているような気もします。
ともかく、これをいろんな風に動かしていくことによって
爆発や煙、水滴や雪などを描画できるようになるのです。

さて、このポイントスプライトの記事はここで終わりですが、課題がまだ1つ残っています。
それは遠近法がなってないということです。
上の画像を注意深く見るとわかることですが、
円の手前と奥の密度が明らかに違います。
奥のポイントスプライトと手前のポイントスプライトの大きさが同じなのです。
これでは、1メートル先にある炎の大きさと、1万キロ先にある炎の大きさが同じと言うことになってしまいます。

この解決法は簡単ですが、技術としては難しいので後に回します。
(解決法が簡単と言うのは、こういうことです。
物はカメラからの距離が2倍になると単純に大きさは1/2、
3倍になると1/3、4倍になると1/4になるので計算は簡単です。)
(技術としては難しいと言うのは・・・
おそらくXNAを難しくしている一番の戦犯、HLSLを使わなければならないと言うことです。)

拍手[1回]


かんたんXNA その16 ワイヤーフレーム

このページは古いです
最新版はこちら

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

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

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

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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionColor[] vertices = new VertexPositionColor[6];

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

        vertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.White);
        vertices[1] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Red);
        vertices[2] = new VertexPositionColor(new Vector3(-1, 0, 0), Color.Navy);

        vertices[3] = new VertexPositionColor(new Vector3(0, 1, -1), Color.White);
        vertices[4] = new VertexPositionColor(new Vector3(1, 0, -1), Color.Red);
        vertices[5] = new VertexPositionColor(new Vector3(-1, 0, -1), Color.Navy);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            effect.VertexColorEnabled = true;
        }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionColor.VertexElements
            );

        graphics.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                2
                );

            pass.End();
        }

        effect.End();
    }
}

xnaWireFrame.JPG(画像が小さくて少しわかりにくいですが、線で三角形が2つ描かれています。)
このサンプルでは、同じ大きさの三角形を手前に1つ、奥に1つ表示しています。
注目すべきなのは、これがワイヤーフレームで、骨組みしか表示されていないと言うことです。

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

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

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


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

拍手[0回]


かんたんXNA その15 かんたんなライティング

このページは古いです
最新版はこちら

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

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


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


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

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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[3];

    ContentManager content;
    Texture2D texture;


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        vertices[0].Position = new Vector3(0, 1, 0);
        vertices[0].TextureCoordinate = new Vector2(0.5f, 0);
        vertices[0].Normal = new Vector3(0, 0, 1);

        vertices[1].Position = new Vector3(1, 0, 0);
        vertices[1].TextureCoordinate = new Vector2(1, 1);
        vertices[1].Normal = new Vector3(0, 0, 1);

        vertices[2].Position = new Vector3(-1, 0, 0);
        vertices[2].TextureCoordinate = new Vector2(0, 1);
        vertices[2].Normal = new Vector3(0, 0, 1);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            effect.TextureEnabled = true;
            effect.EnableDefaultLighting();

            texture = content.Load<Texture2D>("FlyingSpaghettiMonster");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionNormalTexture.VertexElements
            );

        effect.Texture = texture;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );

            pass.End();
        }

        effect.End();
    }
}

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

参考までにライティング無しだとこうなります。
xnaSimplextTriangleTextured.JPG

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

拍手[0回]


かんたんXNA その14 テクスチャ三角形

このページは古いです
最新版はこちら

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

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

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

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

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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    VertexPositionTexture[] vertices = new VertexPositionTexture[3];

    ContentManager content;
    Texture2D texture;


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);

        vertices[0] = new VertexPositionTexture(new Vector3(0, 1, 0), new Vector2(0.5f, 0));
        vertices[1] = new VertexPositionTexture(new Vector3(1, 0, 0), new Vector2(1, 1));
        vertices[2] = new VertexPositionTexture(new Vector3(-1, 0, 0), new Vector2(0, 1));
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            effect.TextureEnabled = true;
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45),
                Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1,
                100
                );
            effect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
            );

            texture = content.Load<Texture2D>("FlyingSpaghettiMonster");
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionTexture.VertexElements
            );

        effect.Texture = texture;

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives <VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );

            pass.End();
        }

        effect.End();
    }
}

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

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

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

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


さて、テクスチャを使った三角形を描画する上で忘れがちなのは、
BasicEffect.TextureEnabledプロパティをtrueにセットすることです。
これによってBasicEffectのテクスチャの描画が有効になります。おそらく内部でエフェクトのテクニックを変更しているのでしょう。(後で確認したところ、どうやら別の方法を使っているようです。))
もしそうしなければ、BasicEffectが描画の時にテクスチャを考慮に入れなくなり、
真っ白の三角形が描かれるのみとなります。
xnaSimplextTriangleTexturedFail.JPG
これをする理由は色付きの三角形を表示するときにBasicEffect.VertexColorEnabledプロパティをtrueにした理由と同じです。
こういったプロパティによって、BasicEffectは内部の描画モードを変更しているのです。

拍手[0回]