忍者ブログ

Memeplexes

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

[PR]

×

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


XNA爆発チュートリアル その1


Xna Creators ClubのParticle 3D Sampleをもっとよく理解するために
1から爆発のパーティクルを作ってみようと思います。

ついでにこれをチュートリアルとしてブログの記事にしてしまおうという魂胆です。
火の玉の時には最後の結果だけでしたからね。




完成予想図(あくまでもイメージです)
3DParticlesExplosion2.jpg
(Particle 3D Sampleより)

いきなり爆発を描画するのはあまりにハードルが高すぎるので、
最初は簡単なやつからいきましょう。

爆発のパーティクルは結局のところ大きさを持った点に(幾何学的には矛盾のある表現です)
爆炎のテクスチャを貼って動かしているだけなので、
もっともシンプルなものは点を表示するだけプログラムであると言えるでしょう!

なので、もっともシンプルな頂点構造体であるVertexPositionColorを点のデータとして
使い、まずいくつか表示してみることにします。
必要なのは点の位置だけなのでVertexPositionColorではColorメンバが無駄になりますが、
一番シンプルなのはこれなのでこれが現状では最善なのです。

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


public class ExplosionTest : Microsoft.Xna.Framework.Game
{
    private GraphicsDeviceManager graphics;

    private BasicEffect effect;
    private VertexPositionColor[] vertices = {
        new VertexPositionColor(new Vector3(0, 0.1f, 0), Color.White),
        new VertexPositionColor(new Vector3(0.1f, 0, 0), Color.White),
        new VertexPositionColor(new Vector3(-0.1f, 0, 0), Color.White)
    };


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

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = new BasicEffect(graphics.GraphicsDevice, null);
            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.VertexElements
                );
        }
    }

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

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

explosionTutorialSimplePoint.jpg(クリックで拡大:白い点が中央に3つ表示されています。)

シンプルです。
自画自賛したくなるくらいのシンプルさです。

このプログラムはウィンドウのクライアント領域の中央に3つの白い点を表示するだけです。

シンプルすぎてこれが爆発とどう結びつくのかわかりにくいかもしれませんが、
この3つの白い点が、大きくなり、テクスチャが貼られ、数が多くなり、周りに向かって広がって動くようになり、
フェードするようになったり、その他いろいろなことをするようになって、
爆発しているように見えるようになるのです。
・・・・・と言うか、そうなる予定です。

拍手[0回]

PR

XNA爆発

Xna Creators Clubのサンプル、Particle 3D Sampleをいじっていたら
障害(?)っぽいものにぶち当たったので問題とその解決、
事の顛末を書いておきます。

Particle 3D Sampleというのは爆発や煙、炎などを
ポイントスプライトで描画するサンプルなのですが、
どうもカメラの距離によっておかしくなっているように感じられたのです。

Particle 3D Sampleのクラスを取り出して
別のプロジェクトで遊んでいたのですが、
カメラを爆発に近づけすぎるとどうやら変になるようなのです。

まず、カメラを爆発から150離れた位置におくと
次のようにきちんと爆発が起こっているように見えます:

explosion150.jpgカメラからの距離:150

これは爆発です。
これは静止画なのでよくわかりませんが、実際にサンプルを実行すると
爆炎がまわりにゆっくりと広がっていくのがわかります。
ここまでは問題ありません。

しかし、カメラをこの爆発に近づけるとどうもおかしなことになるようです。
次の画像はカメラを先ほどの1/10の距離、つまり15離れた位置に
動かしたときの画像です:

explosion15.jpgカメラからの距離:15

ウィンドウにはいくつもの炎のようなものが映っています。
これは別に複数の爆発を起こしたと言うわけではありません。
ここで映っている爆発は1つだけです。

どうやら爆炎がバラバラになって表示されているようです。
爆発を構成している一つ一つの爆炎が本来よりも小さく表示されているのでしょう。
実際、この一つ一つの爆炎はサンプルで使われている
"explosion.png"と同じものです。

ebd0a9be.png
(explosion.png : Particle 3DのContentフォルダ内より)

つまりここでの問題は
爆発に近づいたときに一つ一つの爆炎のポイントスプライトが拡大されない
ことにあるようです。

そのため、遠くから表示するとうまくいっているように見える一方で、
近づいたら、各々の炎が拡大されないために、本来そうであるべきよりも炎が小さく見え、
バラバラになっているように見えるのでしょう。

ここで考えられるのは
もとのサンプルからクラスを取り出すときに何か間違ったことをしてしまったのではないか
ということです。
しかしどうやら元のサンプルでも同じような現象が起きているようなのです。

3DParticlesExplosion.jpg
Particle 3Dで爆発に近づいた場合。やはり爆発がバラバラになっています。
(※見やすくするために爆発の周りの煙は表示していません。)

ちなみに、サンプルでも爆発を遠くから見ると問題なく表示されます。

3DParticlesExplosion2.jpg


ということはこの問題はサンプルの問題だったのでしょうか?
サンプルがポイントのサイズを固定していたのでしょうか?
HLSLのソースコードを読む限りそうではありませんでした。
きちんと、カメラから離れればサイズが小さく、近づけばサイズが大きくなるように
書かれていました。

これはプログラムの実行によっても検証できます。
カメラを爆発からうんと離してやるのです。
もし各ポイントのサイズが固定されているのなら
どんなに離してもそれ以上爆発が小さくならないような
カメラの距離が存在するはずです。
爆発がexplosion.pngの爆炎より小さくなることはありえないはずです。

smallExplosionCamera1500.jpg

小さくなっていますね・・・。
ということはやはり、各ポイントのサイズはカメラの距離によって変わっているようです。

それではどうして近づきすぎるとおかしくなるのでしょう?
これではまるでポイントのサイズの上限が存在しているようではありませんか!
・・・・・・ポイントのサイズの上限?
そういえばPointSizeMaxというプロパティがあったような気がします。
もしかしたらそれが関係しているのかも!


調べてみるとParticleSystemクラスに次のような行が見つかりました:

renderState.PointSizeMax = 256;

とりあえずこれをコメントアウトして実行してみました。
すると、なんとうまく描画されたのです。

bigExplosion.jpgカメラからの距離:15

よし!

問題は解決です。
解決法は
"renderState.PointSizeMax = 256";をコメントアウトする
です。

・・・しかしちょっと待ってください。
このステートメントはいったい何をしていたのでしょうか?
まさかサンプルを実行する人を困らせるためにこんなことをしたわけではないでしょう。
なにか理由があるはずです。

とりあえずこのプロパティを調べてみることにしました。
msdnによると、これはポイントのサイズの上限を表しているのだそうです。
(「そんなものは最初からわかってる!」という声が聞こえてきそうです。)
さらに、デフォルトの値は64.0fなのだそうです。

なるほど、ポイントのサイズはデフォルトでは64が上限と言うことですね。
読めてきました。
ということは、"renderState.PointSizeMax = 256;"というのは
ポイントのサイズの上限を大きくして、より大きなポイントも表示できるようにした
ということなのでしょう。

しかし、そうだとすると新たな疑問が浮かびます。
なぜ"renderState.PointSizeMax = 256;" をコメントアウトして上手くいったのでしょうか?
ポイントサイズの上限を大きくするステートメントをコメントアウトしたのですから、症状は悪化するはずです。
これではアベコベではありませんか!

もともと、ここでの問題は「ポイントのサイズがもっと大きくならない」
というものでした。
そこで「より大きなポイントも表示できるようにする」ステートメントをコメントアウトしたのなら
問題はますますひどくなるはずです。
しかし実際にはこれで問題が解決したのです。

ひとつの可能性として、「実はデフォルトの値は64.0fではなくもっと大きい値だった
ということが考えられます。
たとえばデフォルトの値が実は512.0fくらいで、"renderState.PointSizeMax = 256;"が
ポイントサイズの上限を引き下げていたのかもしれません。

さっそくかんたんなステートメントを追加して調べてみました。
LoadGraphicsContentメソッド内に次の行を追加したのです。

Window.Title += "maxdefault:" + graphics.GraphicsDevice.RenderState.PointSize;

これでウィンドウのタイトルにPointSizeプロパティのデフォルトの値が表示されるはずです。
結果は"maxdefault:8192"でした。
デフォルトの値は8192です。
・・・あれ、何でこんなに大きいんだろ・・・・・・?
びっくりです。
予想では512、大きくてもせいぜい1024くらいだと思っていたのですがそれを大きく上回りました。
ぼくがこのとき使ったディスプレイの解像度は1920 x 1200ですから上限など無いも同然です。
ディスプレイの4倍以上の大きさのポイントを描画できるわけですからね。
これでは "renderState.PointSizeMax = 256;"なんてしたらおかしくなるのもうなずけます。

気にかかるのはこの値がmsdnの記述と大きくかけ離れている事実です。
8192と64では128倍の差があるではありませんか!
どう考えてもおかしいです。

この理由はわかりませんが、ぼくは次のように考えました:

msdnのPointSizeMaxプロパティのページをさらに見ると、
このプロパティはGraphicsDeviceCapabilities.PointSizeMaxの値を
超えられないとも書いてあります。
グラフィックスデバイスには表示できるポイントの大きさに限界があって、
それをこのプロパティが表しているのでしょう。

ということは、もしかするとRenderState.PointSizeMaxは
グラフィックスデバイスの能力を調べて、自動的に上限を
最大の値にまで引き上げてくれるのかもしれません。

おそらくこのパソコンのグラフィックスデバイスのポイントサイズの上限が8192だったのでしょう。(後で調べてみると、実際そうでした)
それをRenderState.PointSizeMaxは読み取って、
自動的に最大限の機能を発揮できるようにしてくれたのかもしれないというわけです。
別のパソコンならRenderState.PointSizeMaxからはまた違った値が返されるでしょう。

蛇足

msdnのサンプルを参考にして、グラフィックスデバイスのポイントサイズの上限を調べるのに使ったソースコードを書いておきます。
            foreach (GraphicsAdapter adapter in GraphicsAdapter.Adapters)
            {
                GraphicsDeviceCapabilities caps = adapter.GetCapabilities(DeviceType.Hardware);
                Window.Title += "max:" + caps.MaxPointSize;
            }

グラフィックスデバイスの能力を調べるには
Microsoft.Xna.Framework.Graphics.GraphicsDeviceCapabilitiesクラスを使います。

[Serializable]
public sealed class GraphicsDeviceCapabilities : IDisposable


このクラスはポイントサイズの上限のほかにも、使用できるテクスチャの大きさの上限や、
頂点のインデックスの上限も調べることが出来ます。

ポイントサイズの上限を表すプロパティは、MaxPointSizeです。

public float MaxPointSize { get; }

このクラスのインスタンスを得るには、GraphicsAdapter.GetCapabilitiesメソッドを使います。

public GraphicsDeviceCapabilities GetCapabilities ( 
        DeviceType deviceType
)


このメソッドは、引数のdeviceTypeに指定された種類の
デバイスの能力を表すGraphicsDeviceCapabilitiesのインスタンスを返します。

DeviceTypeは列挙型で、3つのメンバ、つまりHardware, NullReference, Referenceを持ちます。
ここではHardwareを使いました。

このメソッドを持っているのはグラフィックスアダプタを表す、
Microsoft.Xna.Framework.Graphics.GraphicsAdapterクラスです。(そのまんまですが)

このクラスのインスタンスを得るには、コンストラクタではなく
自分のクラスのAdapters静的プロパティを使います。

public static ReadOnlyCollection<GraphicsAdapter> Adapters { get; }

このプロパティの中にシステムで使うことの出来る全てのグラフィックス・アダプタが入っています。
foreachなんかでまわして使うと良いでしょう。

拍手[0回]


XNA火の玉

XNA Creators Clubのサンプルを参考にして火の玉を作ってみました。
これはポイントスプライトを使っていて、
各頂点(?)にサンプルから拝借したfire.pngを貼り付けています。

fireball1.jpg

おもうに、公式のサンプルは長すぎて難しいですね。

Fireball.zip(ソースコード)

拍手[0回]


かんたんXNA HLSL編 その7 テクスチャ

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

今回はテクスチャです。
ポリゴンの上に貼り付けるテクスチャを扱います。
つまりBasicEffectで言うとTextureプロパティ関連でやっていたことです。
ただしはるかに柔軟なことが出来ます。


大まかな流れを言うと、

「まずHLSL側でtexture型のグローバル変数を宣言し、
C#側からそれにインスタンスをセットし、
ピクセルシェーダでそのテクスチャから
相当するテクスチャ座標の色をサンプルして出力する」、

といったぐあいです。


これをやると「ピクセルの色は自分が完全に決めているんだなぁ」という実感が持てると思います。
ちょこっとプログラムを書き換えると水面の波のような効果を出すことも出来ます。


HLSLでテクスチャを宣言するにはこうします:

texture [変数名];

このテクスチャのテクセル(テクスチャのピクセル)の色を、
ピクセルシェーダで使います。
つまり、ピクセルシェーダの引数として与えられるテクスチャ座標に
対応する部分のテクスチャの色をtex2D関数を使って得て、戻り値として返すのです。

float4 tex2D ( sampler samplerState, float2 textureCoordinate )

textureCoordinateはテクスチャ上の座標を意味します。ここに指定した座標の色が返されます。
この関数の戻り値は色を表すfloat4です。
こいつをピクセルシェーダに使うことになるでしょう。

ただし、最初の引数samplerというのを見てわかるように、
初学者にとっては困ったことですが、このままではtex2D関数を使うことが出来ません。
テクスチャからどのように色を取り出す(サンプルする)かを
表すサンプラを宣言しなければならないのです。
このサンプラしだいで表示されるテクスチャがギザギザになったり滑らかになったりします。

しかしまあ、最初は一番シンプルな例を見るべきでしょう。
ギザギザにはなってしまいますが。

sampler [変数名] = sampler_state
{
        Texture = <テクスチャの変数名>;
};

「テクスチャの変数名」というのは、このサンプラが使うテクスチャです。
(HLSLの将来のバージョンではこれを設定する必要はなく、
サンプラとテクスチャは(正当にも)独立して扱えるようですが、
現時点では必ずテクスチャを指定してやらねばなりません。)

テクスチャの変数名は < > で囲ってやらなければなりません。(でも試したところ ( ) でもいけました。)
単に変数名を書いただけではコンパイルエラーになります。
(まあ、初期化が行われるときには変数にインスタンスは入っていないでしょうから正当です。
C#側からセットして初めてテクスチャの変数がつかえるようになるわけですからね。)

もしかしたら < > にはC/C++でいう変数のポインタみたいな意味があるのかもしれません。(わかりませんが)

この { } の中では他にも
テクスチャからサンプルする色を滑らかにするフィルターの
設定が出来るのですが、それは後で述べることにします。

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


public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;

    VertexPositionTexture[] vertices = {
        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))
    };


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

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");
            Matrix view = Matrix.CreateLookAt(
                new Vector3(1, 0, 1), 
                new Vector3(),
                new Vector3(0, 1, 0)
                );
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90),
                (float)Window.ClientBounds.Width/Window.ClientBounds.Height,
                0.1f, 100
                );
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["MyTexture"].SetValue(content.Load<Texture2D>("Xnalogo"));

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

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

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

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

hlslTesture.jpg(公式のXnalogo.pngを拝借してきました。クリックで拡大)
テクスチャがちゃんと張り付いていますね。

しかし、よくみると、テクスチャがギザギザしています。
一番近い"a"のところなんか荒すぎてすりガラスのようです。
手前はテクスチャが拡大されるので、そのように見えるのです。
これを治し滑らかにするには、サンプラでフィルターを指定してやります。

使うフィルターはMagFilter (Magnification filter)、
テクスチャが拡大されるときに使われえるフィルターです。
これをLinearにセットすると、拡大したとき滑らかに表示されます。
フィルターにセットできる値は、C#でのTextureFilter列挙型に対応しているようです。

定数名 解説
None  「MipMapは無効になり、ラスタライザはかわりにMagFilterを使います。」だそうです。
Point  滑らかになりません。一番近いテクセルの色が使われます。ギザギザです。
Linear  バイリニアフィルターです。周りの2x2の領域の中での平均値が使われます。いろんなサンプルを見たところ、これが一番使われているようですね。
Anisotropic  異方性フィルターです。
PyramidalQuad  4サンプル・テントフィルターです。
GaussianQuad  4サンプル・ガウシアンフィルターです。画像をぼかすことで滑らかにするそうです。

とりあえずLinearを使えば良いようです。
ほとんどのサンプルがLinearをフィルターとして使っています。

float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

magLinear.jpg
近くの文字、"a"が滑らかになりました。

しかしやっぱり、よく見ると変です。
確かに近くの文字は滑らかになりましたが、遠くの文字はまだ荒れています。
"x"の下の部分がなんだかギザギザしていますね。

遠くの文字は遠近法によって縮小されるからです。
MagFilterをセットすることによってテクスチャを拡大したときには滑らかになったものの、
テクスチャを逆に縮小したときにはまだギザギザしてしまうのです。

テクスチャが縮小されるときに使われるフィルターはMinFilter (Minification Filter)です。
これにLinearをセットすると、テクスチャが縮小されたときも滑らかになります。
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
	MinFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

minMagFilter.jpg
xの字もなめらかになりました。

拍手[0回]


XNA Game Studio 2.0

XNA Game Studio 2.0の予告の解説みたいなのはひにけにXNAさんででているようなので、
ここではリンクを貼るだけにします。(全部英語ですが)

Microsoft XNA Developer Presentations(英語)

Xnaに関する動画があります。
英語ですが、時々デモがあって、雰囲気を味わうことが出来るかもしれません。
ここに、Xna Game Studio 2.0の解説みたいなのがあります。

Gamefest 2007: What's New in XNA Game Studio 2.0

Xna Game Studio 2.0で新しいこと

Networking with the XNA Framework

2.0で新しく出るネットワーク機能のデモみたいです。

拍手[0回]