忍者ブログ

Memeplexes

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

XNA爆発チュートリアル その6 動かす

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

前回でBasicEffectから、HLSLで書いたカスタムエフェクトへの移行が済んだので、今回からはさらにいろんなことが出来るようになります。

まずは爆発を動かしてみましょう。
中心から、周囲に「ぶわっ」っと広がっていくような感じです。

これをやるためにエフェクトファイルにTimeというグローバル変数を用意して、
その値にしたがってそれぞれの爆炎を頂点シェーダで動かしていくことにします。

ただ、これをやるためには頂点の構造体の
メンバに速度が含まれていなければならないのですが、
これはとりあえず位置の情報を流用することにします。
(臭う!コードの臭いです!でも訂正は後回しです)

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


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

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

    private ContentManager content;


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

    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,
                VertexPositionColor.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<VertexPositionColor>(
                PrimitiveType.PointList,
                vertices,
                0,
                vertices.Length
                );

            pass.End();
        }

        effect.End();
    }
}

ExplosionShader.fx
float Time;

texture ExplosionTexture;

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

float4 ExplosionVertexShader(float4 position:POSITION):POSITION
{
	return float4(position.xyz * Time, 1);
}

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


("return position * Time;"ではなくて"return float4(position.xyz * Time, 1);"
なのはそうしないとうまくいかないからです。float4の最後は1でなくてはいけません。
この値をたとえば2にすると爆発の速さが1/2になってしまいます。
大きくすると速さが遅くなっていくのです。
逆に、小さくすると速くなります。
xyzを拡大し、wも拡大すると、ちょうど互いに影響を打ち消しあい、結局爆発は動かなくなります。
"return position * Time;"では動かないように見えるのです。)



explosionTutorialExpanding1.jpg
explosionTutorialExpanding2.jpg
explosionTutorialExpanding3.jpg
うまくいきました。
3つの爆炎が時間の経過とともに周りへ広がっていきます。

ここで気になるのは、変数名とその役割が一致していないことです。
どういうわけか、位置を表す変数に速度が入ってしまっています。
これはいただけません。
直すことにしましょう。

C#側で新しい頂点の構造体を用意して、そのメンバの名前をVelocity(速度)にします。
HLSL側での変更は変数名の変更だけで良いでしょう。(position → velocity)

VertexVelocity.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


struct VertexVelocity
{
    public Vector3 Velocity;

    public VertexVelocity(Vector3 velocity)
    {
        this.Velocity = velocity;
    }

    public static readonly VertexElement[] VertexElements = {
        new VertexElement(
            0,
            0,
            VertexElementFormat.Vector3,
            VertexElementMethod.Default,
            VertexElementUsage.Color,
            0)
    };
}

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 = {
        new VertexVelocity(new Vector3(0, 0.1f, 0)),
        new VertexVelocity(new Vector3(0.1f, 0, 0)),
        new VertexVelocity(new Vector3(-0.1f, 0, 0))
    };

    private ContentManager content;


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

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

ExplosionShader.fx
float Time;

texture ExplosionTexture;

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

float4 ExplosionVertexShader(float4 velocity:COLOR):POSITION
{
	return float4(velocity.xyz * Time, 1);
}

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


実行結果はさっきと同じです。
さっきと違うのは、その内部です。
変数の名前と目的をだいたい一致させました。

「だいたい」と言うのは、(名前ではないですが)
セマンティクスを一致させることが出来ず、(VELOCITYがないため)
しかたがなくCOLORを使ったのです。
でもまあ、「色 → 特色 → 頂点の特色 → ここでは速度」という風に、
連想ゲームでたどり着けないこともないので
ここはおおめに見ても良いかもしれませんね。


ともかく!これでうまくリファクタリング(?)できました。
いままで頂点の構造体にはVertexPositionColorを使っており、
使っていない、無駄なメンバ(Color)があったのです。
今、それが取り除かれ、本当に必要な速度だけをメンバに持つ、
VertexVelocity構造体が作られました。
これでさらなる変更を加えやすくなったはずです。

つまり、
爆発を表しているそれぞれの頂点をフェードアウトさせたり、
それぞれの爆炎を回転させたり、
その他いろいろな変更を加えやすくなり、
結果としてより簡単に本物の爆発に近づくことが出来るのです。
(というか、そうなる予定です。)

拍手[0回]

PR