忍者ブログ

Memeplexes

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

XNA爆発チュートリアル その7 頂点を増やしてみる

XNA爆発チュートリアル その6のつづきです。

前回は爆炎を初めて動かしてみました。

今回はこれをもっと派手にしてみましょう。
頂点(爆炎)を増やしてみます。
前回までは爆炎は3つだけでしたが、ここではその10倍の30個にしてみます。
(なんで30個かというと、Xna Creators ClubのParticle 3Dサンプルの爆発で使われていた数(Particle3DSample.Projectile.numExplosionParticles)がちょうど30だったからです。)

そして、各頂点の速度をあらわすVelocityメンバはランダムに初期化することにします。

ひとまず、この変更はC#側だけですね。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


public class ExplosionTest : Game
{
    private GraphicsDeviceManager graphics;

    private Effect effect;
    private VertexVelocity[] vertices;

    private ContentManager content;
    private static System.Random random = new System.Random();


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

    protected override void Initialize()
    {
        vertices = new VertexVelocity[30];

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i].Velocity = randomVector3()/10;
        }

        base.Initialize();
    }

    Vector3 randomVector3()
    {
        return new Vector3(
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1)
            );
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("Content/ExplosionShader");
            effect.Parameters["ExplosionTexture"].SetValue(
                content.Load<Texture2D>("Content/explosion")
                );

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

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

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

        graphics.GraphicsDevice.RenderState.PointSize = 50;
        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;

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

        effect.Parameters["Time"].SetValue(
            (float)gameTime.TotalGameTime.TotalSeconds
            );

        effect.Begin();

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

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

            pass.End();
        }

        effect.End();
    }
}


explosionTutorialManyVertices1.jpg
explosionTutorialManyVertices2.jpg

にぎやかになってきましたね。
なんといっても、爆炎の数を30に増やしたのですから当然です!

かく爆炎の速度はランダムに、System.Randomクラスで決めています。
速度成分X, Y, Zそれぞれが、-1 / 10 から1 / 10 の範囲内になるようになっています。

しかしここには明らかに2つ、問題があります。

1つめは、「爆炎の周りに透明な四角形が見える」ということです。
これは、それぞれの深度テストの問題です。
すぐに問題は解決するでしょう。
各頂点を描画するときに深度バッファに書き込まないようにすればいいのです。

2つめは、「爆炎の数が明らかに、(総頂点数の)30より少ない」ということです。
せいぜい10個かそのくらいです。
あとの10~20個はどこへいったのでしょうか?

それは、描画されない領域です。
つまり、描画されるZの範囲は0~1までなので、
そこからはみ出てしまった約20個の爆炎は描画されないということです。
BasicEffectでこの問題を解決していたのはProjectionプロパティで、
各頂点のZの値を、うまく0~1の領域に押し込んでいたのです。
解決するにはエフェクトファイルでBasicEffectのProjectionに
相当するものを作ってやる必要があるでしょう。

この問題を解決することにしましょう。
まずは1つめの問題、透明な四角を消すことからはじめます。
これは透明な四角の領域の深度バッファが更新されて、
それ以後そこに新たな爆炎が描画されなくなってしまうことが原因なので、
爆発を描画する間は深度バッファが書き込まれないようにします。
RenderState.DepthBufferWriteEnableをfalseにするのです。
(ただ、これには副作用もあります。この方法で爆発を描画した後、より遠いところに何か別のものを描画した場合、上書きされてしまい、前後関係がおかしくなります。このテクニックによる描画はなるべく最後に回したほうが良いでしょう。)


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


public class ExplosionTest : Game
{
    private GraphicsDeviceManager graphics;

    private Effect effect;
    private VertexVelocity[] vertices;

    private ContentManager content;
    private static System.Random random = new System.Random();


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

    protected override void Initialize()
    {
        vertices = new VertexVelocity[30];

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i].Velocity = randomVector3()/10;
        }

        base.Initialize();
    }

    Vector3 randomVector3()
    {
        return new Vector3(
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1)
            );
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("Content/ExplosionShader");
            effect.Parameters["ExplosionTexture"].SetValue(
                content.Load<Texture2D>("Content/explosion")
                );

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

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

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

        graphics.GraphicsDevice.RenderState.PointSize = 50;
        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;

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

        effect.Parameters["Time"].SetValue(
            (float)gameTime.TotalGameTime.TotalSeconds
            );

        effect.Begin();

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

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

            pass.End();
        }

        effect.End();
    }
}

explosionTutorialDepthBufferWriteDisabled.jpg
透明な四角形が消えました。

次は爆炎が消えてしまう問題を解決します。
この問題を解決するには、Projectionマトリックスを使って、
描画されるZの領域を0~1からもっと大きく、
たとえば0.1f~1000というように広げてやればいいでしょう。
(正確には最終的に描画される領域は変わりません。Projectionマトリックスは頂点データを変換するだけです。0.1f~1000を0~1に変換するというわけです)

ただ、このシチュエーションでこの方法を使うと、カメラと爆発が重なってしまうため、
カメラの位置を表すViewマトリックスも使ってカメラをもう少し手前にずらすべきでしょう。

この2つのマトリックス、ProjectionとViewを使って、頂点データを変換してやります。

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


public class ExplosionTest : Game
{
    private GraphicsDeviceManager graphics;

    private Effect effect;
    private VertexVelocity[] vertices;

    private ContentManager content;
    private static System.Random random = new System.Random();


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

    protected override void Initialize()
    {
        vertices = new VertexVelocity[30];

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i].Velocity = randomVector3()/10;
        }

        base.Initialize();
    }

    Vector3 randomVector3()
    {
        return new Vector3(
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1),
            (float)(random.NextDouble() * 2 - 1)
            );
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("Content/ExplosionShader");
            effect.Parameters["ExplosionTexture"].SetValue(
                content.Load<Texture2D>("Content/explosion")
                );

            effect.Parameters["View"].SetValue(
                Matrix.CreateLookAt(
                    new Vector3(0, 0, 5), new Vector3(), new Vector3(0, 1, 0)
                    )
                );
            effect.Parameters["Projection"].SetValue(
                Matrix.CreatePerspectiveFieldOfView(
                    MathHelper.ToRadians(45),
                    aspectRatio,
                    0.1f, 1000
                    )
                );


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

    float aspectRatio
    {
        get
        {
            return (float)graphics.GraphicsDevice.Viewport.Width 
                / graphics.GraphicsDevice.Viewport.Height;
        }
    }

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

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

        graphics.GraphicsDevice.RenderState.PointSize = 50;
        graphics.GraphicsDevice.RenderState.PointSpriteEnable = true;

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

        effect.Parameters["Time"].SetValue(
            (float)gameTime.TotalGameTime.TotalSeconds
            );

        effect.Begin();

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

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

            pass.End();
        }

        effect.End();
    }
}

ExplosionShader.fx
float4x4 View;
float4x4 Projection;

float Time;

texture ExplosionTexture;

sampler explosionSampler = sampler_state
{
	Texture = <ExplosionTexture>;
};

float4 ExplosionVertexShader(float4 velocity:COLOR):POSITION
{
	float4 worldPosition = float4(velocity.xyz * Time, 1);
	return mul(worldPosition, mul(View, Projection));
}

float4 ExplosionPixelShader(float2 textureCoordinate:TEXCOORD):COLOR
{
	return tex2D(explosionSampler, textureCoordinate);
}

technique ExplosionShaderTechnique
{
	pass P0
	{
		VertexShader = compile vs_2_0 ExplosionVertexShader();
		PixelShader = compile ps_2_0 ExplosionPixelShader();
	}
}


explosionTutorialTransformedByMatrices.jpg
静止画ではわかりにくいですが、
だいぶそれらしくなってきました。

最初の数秒間はまさに「爆発」って感じです!
かなり前進したといえるでしょう。

これを今後、それぞれの爆炎をフェードアウトさせたり、
フェードアウトするにしたがって回転させたり、
その他色々なことをすることによって
より本物の爆発らしくしていくのです!
(というか、そうなる予定です)


















拍手[0回]

PR