XNA UK User Group

A helping hand for bedroom coders throughout the land.
in

RandomChaos

3D PointSprite Particle Tutorial I

http://www.youtube.com/v/AjsQiYaZ-po <p><a href="http://www.youtube.com/v/AjsQiYaZ-po">http://www.youtube.com/v/AjsQiYaZ-po</a></p>  

Was browsing the boards as I do and came across a thread on Ziggyware where a member was looking for a Particle Engine tutorial, at first I did not quite get what he wanted, so tried to clarify what he wanted with a post and suggested I could create a simple particle library, to which another member and the original poster said it would be a good idea and so I though the best thing would be a very simple tutorial describing what I know of particle system.

Now, I am no expert but I think I have an idea of the basic principles of a particle system and so I am going to put up a series of tutorials describing how to create a simple particle system. I know I have already got 2 samples up here, but it it just holds code samples and no explanation of what is going on under the hood.

In this series I am going to do both a 3D and 2D tutorial. As I already have a 3D sample I will start with the 3D system. In this first tutorial I am going to cover a PonitSprite particle system.

So, where do I start when creating a particle system, well I guess the best place is the particle it's self, to me this means the data structure I am going to use to hold the data required to draw and move my particle, think about what you want the particle to represent, what data you will want to store to draw the particle. So I create my own vertex element, this describes the primitive to be used to draw the particle. Now this is a tutorial so I am going to be quite generic in my particle description, it is going to contain what I think are the basic elements for any particle, you will probably want to add more to this structure for your system(s) or this might just do the job. It is going to hold position, colour, sprite size and an alpha value.

So I create a new source file in my project and name it after the structure, in this case I have called it PointSpriteElement.cs. In the file I create a new structure definition

  public struct PointSpriteElement
  {
  }

 

 

I now add my elements

  public struct PointSpriteElement
  {
      Vector3 position;
      Color color;
      Vector2 data;
  }

So, my position is a vector 3 and will store this particles position in space, colour (I am in the habit of using the American spelling now) will store the color I want to draw the particle in and data is a vector 2 that is going to hold my sprite size in the X and the alpha value in the Y.

I now give this structure some properties so I can access the particle elements.

    public struct PointSpriteElement
    {
        Vector3 position;
        Color color;
        Vector2 data;

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector2 Data
        {
            get { return data; }
            set { data = value; }
        }

        public Color Color
        {
            get { return color; }
            set { color = value; }
        }

        public float SpriteSize
        {
            get { return data.X; }
            set { data.X = value; }
        }
        public float AlphaValue
        {
            get { return data.Y; }
            set { data.Y = value; }
        }
    }

I now need to create the definition of the particle that will be used by my shader, this is done using a VertexElement array.

public static readonly VertexElement[] VertexElements = new VertexElement[] 
{
    new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
    new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0),                
    new VertexElement(0, sizeof(float)*4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),                
};

Right, as you can see this looks a bit messy, but all it is doing is describing my vertex input structure, or I guess a simpler description is how the particle is passed to the shader. So the first line describes the vector 3 that is used for the position, the first parameter is the stream to be used and can be used for stuff like multi texturing as it saves the duplication of vertex data (see this link for a better description), I have only ever used 0. As this is the first element in the element array it has an offset of 0, a format of Vector 3 as that is the data type, the method is default, usage is Position and the channel is 0. So in the shader this would be described as half3 Position : Position0; And so the other to elements are described in the same vein, you can see the offset (second parameter) increased by the last elements size.

I also include a property so I don't have to keep typing in the particle element size.

public static int SizeInBytes
{
    get
    {
        return sizeof(float) * 6;
    }
}

So this just returns the size if the structure, which in this case is the size of 6 floats. This could also be described like this (3 + 1 + 2) * 4.

The whole structure now looks like this:

    public struct PointSpriteElement
    {
        Vector3 position;
        Color color;
        Vector2 data;

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector4 Data
        {
            get { return data; }
            set { data = value; }
        }

        public Color Color
        {
            get { return color; }
            set { color = value; }
        }

        public float SpriteSize
        {
            get { return data.X; }
            set { data.X = value; }
        }
        public float AlphaValue
        {
            get { return data.Y; }
            set { data.Y = value; }
        }
  
        public static readonly VertexElement[] VertexElements = new VertexElement[] 
        {
            new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
            new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0),                
            new VertexElement(0, sizeof(float)*4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),                
        };

        public static int SizeInBytes
        {
            get
            {
                return sizeof(float) * 6;
            }
        }
    }

 

Right we now have a definition for our particle that is going to be used in the shader to draw our particle in the game world. We now need an array of these particles and a class to manage this particle array. In my project I create a new source file called PointSpriteParticleEmitter.cs, this is going to hold my particle emitter class. Again, you need to think about what kind of particle emitter you want to create, in this tutorial I am going to create a simple trail.

So we start by creating our class

public class PointSpriteParticleEmitter : DrawableGameComponent
{
}

Now, like any other object in the game world we will need a position a scale and rotation. I am also going to give it a color so I can set all the particle colors if I wish. We will also need a variable to store the number of particles to have in this instance. I also have in this emitter some variables for my particle physics, I will go into more detail in the Update override. Also, we need an Effect object to load the shader into. Oh, and a Game object to bind to the calling game class.

So the class now looks like this

public class PointSpriteParticleEmitter : DrawableGameComponent
{
    public PointSpriteElement[] particleArray;

    public Vector3 myPosition;
    public Vector3 myScale;
    public Quaternion myRotation;

    public Color particleColor;
    
    public int partCount;

    Effect shader;

    Game game;

    int nextParticle = 0;
    Vector3 targetPos;
    Vector3 myLastpos;
}

Now for the constructor (ctor), very simple, the calling game and the particle count are passed and used to build the emitter and all values are set to arbitrary default values.

public PointSpriteParticleEmitter(Game game, int particleCount) : base(game)
{
    this.game = game;
    myPosition = Vector3.Zero;
    myScale = Vector3.One;
    myRotation = new Quaternion(0, 0, 0, 1);

    partCount = particleCount;

    particleColor = Color.White;
}

We are inheriting from DrawableGameComponent so we now need to override some of the base methods to load, update and draw our particles. Lets start with LoadContent. In this method we are going to load the shader, texture and load our particle array. I will go into more detail regarding the shader and the particle texture later on, but for now I will concentrate on the loading of our particles. We are also setting up some of the particle physics variables ready for use. Also, at the top of the method we will setup the vertex declaration on the graphics device.

protected override void LoadContent()
{
    game.GraphicsDevice.VertexDeclaration = new VertexDeclaration(game.GraphicsDevice, PointSpriteElement.VertexElements);

    shader = game.Content.Load<Effect>("Shaders/PointSpriteParticleShader");
    shader.Parameters["particleTexture"].SetValue(game.Content.Load<Texture2D>("Textures/particle"));
    
    targetPos = myPosition;
    myLastpos = targetPos;

    LoadParticles();
}

I have put the loading of particles into a separate method as you might want to call it again at a later date. So our method to load the particle array is going define the size of out particle array, and set each particle up ready for use in the system. Now in here if you were using it for say a weather effect like rain or snow you would want to set the positions to random values in the scene, as I say it all depends on what you want the effect to achieve.

My LoadParticles method looks like this

private void LoadParticles()
{
    particleArray = new PointSpriteElement[partCount];

    for (int i = 0; i < particleArray.Length; i++)
    {
        particleArray[i].Position = myPosition;
        particleArray[i].Color = Color.Black;
        particleArray[i].SpriteSize = 1f;                
    }
}

Pretty simple stuff, we are setting the position of each particle to the emitters position, then (as in this example we are going to do additive blending and so the alpha value is redundant, but will show that in the next tutorial) setting the particle color to black and it's size to 1.

Now for the fun bit, this is where the behaviour of your particle emitter will be defined. In this tutorial we are doing a trail so we have a target position we are chasing, we need to know what the targets last position was, so that too is stored as well as the current particle we want to work with. I have been terming this particle physics, this is a very lose term, what I really mean is particle behaviour. In this sample we are doing all the particle physics on the CPU, in a later tutorial I will show how you can move this to the GPU.

So here is the work horse of the emitter, the Update override.

public override void Update(GameTime gameTime)
{
    for (int p = 0; p < particleArray.Length; p++)
    {
        if (p == nextParticle && myLastpos != myPosition)
        {
            particleArray[p].Position = myLastpos;
            particleArray[p].Color = particleColor;                        
        }                
        particleArray[p].SpriteSize = 1 - (Vector3.Distance(particleArray[p].Position, targetPos)/25);
    }
    nextParticle++;

    if (nextParticle >= particleArray.Length)
        nextParticle = 0;

    myLastpos = targetPos;
    targetPos = new Vector3((float)Math.Cos(gameTime.TotalGameTime.TotalSeconds) * 10, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 10, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds));
}

All we are doing here is moving through our particle array, finding the particle we want to work with and setting it's new position and color, also we are sizing the particle based on how far it is from the target so giving a tapering effect to the trail. We then store the last position of the target and (as this is a tutorial and I don't want to code another object to use as a target) we move the target.

And now onto the draw method. This is where we are going to define out blending method, in this tutorial, additive blending. We are also setting up a few parameters in the shader, but I will explain those in the shader code it's self in a short while.

public override void Draw(GameTime gameTime)
{
    bool PointSpriteEnable = game.GraphicsDevice.RenderState.PointSpriteEnable;

    float PointSize = game.GraphicsDevice.RenderState.PointSize;

    bool AlphaBlendEnable = game.GraphicsDevice.RenderState.AlphaBlendEnable;
    Blend DestinationBlend = game.GraphicsDevice.RenderState.DestinationBlend;
    Blend SourceBlend = game.GraphicsDevice.RenderState.SourceBlend;
    bool DepthBufferWriteEnable = game.GraphicsDevice.RenderState.DepthBufferWriteEnable;
    BlendFunction BlendFunc = game.GraphicsDevice.RenderState.BlendFunction;

    game.GraphicsDevice.RenderState.PointSpriteEnable = true;

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

    game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

    Matrix wvp = (Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition)) * Camera.myView * Camera.myProjection;

    shader.Parameters["Projection"].SetValue(Camera.myProjection);
    shader.Parameters["ViewportHeight"].SetValue(Camera.myViewport.Height);
    shader.Parameters["WorldViewProj"].SetValue(wvp);

    shader.Begin();
    for (int ps = 0; ps < shader.CurrentTechnique.Passes.Count; ps++)
    {
        shader.CurrentTechnique.Passes[ps].Begin();
        game.GraphicsDevice.DrawUserPrimitives<PointSpriteElement>(PrimitiveType.PointList, particleArray, 0, particleArray.Length);
        shader.CurrentTechnique.Passes[ps].End();
    }
    shader.End();

    game.GraphicsDevice.RenderState.PointSpriteEnable = PointSpriteEnable;

    game.GraphicsDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;

    game.GraphicsDevice.RenderState.DestinationBlend = DestinationBlend;
    game.GraphicsDevice.RenderState.SourceBlend = SourceBlend;

    game.GraphicsDevice.RenderState.BlendFunction = BlendFunc;

    game.GraphicsDevice.RenderState.DepthBufferWriteEnable = DepthBufferWriteEnable;

    base.Draw(gameTime);
}     

So our emitter class now looks like this

public class PointSpriteParticleEmitter : DrawableGameComponent
{
    public PointSpriteElement[] particleArray;

    public Vector3 myPosition;
    public Vector3 myScale;
    public Quaternion myRotation;

    public Color particleColor;
    
    public int partCount;

    Effect shader;

    Game game;

    int nextParticle = 0;
    Vector3 targetPos;
    Vector3 myLastpos;

    public PointSpriteParticleEmitter(Game game, int particleCount) : base(game)
    {
        this.game = game;
        myPosition = Vector3.Zero;
        myScale = Vector3.One;
        myRotation = new Quaternion(0, 0, 0, 1);

        partCount = particleCount;

        particleColor = Color.White;
    }
    
    protected override void LoadContent()
    {
        game.GraphicsDevice.VertexDeclaration = new VertexDeclaration(game.GraphicsDevice, PointSpriteElement.VertexElements);

        shader = game.Content.Load<Effect>("Shaders/PointSpriteParticleShader");
        shader.Parameters["particleTexture"].SetValue(game.Content.Load<Texture2D>("Textures/particle"));
        
        targetPos = myPosition;
        myLastpos = targetPos;

        LoadParticles();
    }

    private void LoadParticles()
    {
        particleArray = new PointSpriteElement[partCount];

        for (int i = 0; i < particleArray.Length; i++)
        {
            particleArray[i].Position = myPosition;
            particleArray[i].Color = Color.Black;
            particleArray[i].SpriteSize = 1f;                
        }
    }
    
    public override void Update(GameTime gameTime)
    {
        for (int p = 0; p < particleArray.Length; p++)
        {
            if (p == nextParticle && myLastpos != myPosition)
            {
                particleArray[p].Position = myLastpos;
                particleArray[p].Color = particleColor;                        
            }                
            particleArray[p].SpriteSize = 1 - (Vector3.Distance(particleArray[p].Position, targetPos)/25);
        }
        nextParticle++;

        if (nextParticle >= particleArray.Length)
            nextParticle = 0;

        myLastpos = targetPos;
        targetPos = new Vector3((float)Math.Cos(gameTime.TotalGameTime.TotalSeconds) * 10, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 10, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds));
    }
    public override void Draw(GameTime gameTime)
    {
        bool PointSpriteEnable = game.GraphicsDevice.RenderState.PointSpriteEnable;

        float PointSize = game.GraphicsDevice.RenderState.PointSize;

        bool AlphaBlendEnable = game.GraphicsDevice.RenderState.AlphaBlendEnable;
        Blend DestinationBlend = game.GraphicsDevice.RenderState.DestinationBlend;
        Blend SourceBlend = game.GraphicsDevice.RenderState.SourceBlend;
        bool DepthBufferWriteEnable = game.GraphicsDevice.RenderState.DepthBufferWriteEnable;
        BlendFunction BlendFunc = game.GraphicsDevice.RenderState.BlendFunction;

        game.GraphicsDevice.RenderState.PointSpriteEnable = true;

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

        game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

        Matrix wvp = (Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition)) * Camera.myView * Camera.myProjection;

        shader.Parameters["Projection"].SetValue(Camera.myProjection);
        shader.Parameters["ViewportHeight"].SetValue(Camera.myViewport.Height);
        shader.Parameters["WorldViewProj"].SetValue(wvp);

        shader.Begin();
        for (int ps = 0; ps < shader.CurrentTechnique.Passes.Count; ps++)
        {
            shader.CurrentTechnique.Passes[ps].Begin();
            game.GraphicsDevice.DrawUserPrimitives<PointSpriteElement>(PrimitiveType.PointList, particleArray, 0, particleArray.Length);
            shader.CurrentTechnique.Passes[ps].End();
        }
        shader.End();

        game.GraphicsDevice.RenderState.PointSpriteEnable = PointSpriteEnable;

        game.GraphicsDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;

        game.GraphicsDevice.RenderState.DestinationBlend = DestinationBlend;
        game.GraphicsDevice.RenderState.SourceBlend = SourceBlend;

        game.GraphicsDevice.RenderState.BlendFunction = BlendFunc;

        game.GraphicsDevice.RenderState.DepthBufferWriteEnable = DepthBufferWriteEnable;

        base.Draw(gameTime);
    }     
}

Now we have a particle system we need to implement it, as we have derived our emitter from DrawableGameComponent this is pretty easy. We just create an instance of the object and add it to the game components like this

public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    PointSpriteParticleEmitter emitter1 = new PointSpriteParticleEmitter(this, 100);
    emitter1.myPosition = new Vector3(0, 0, -20);
    Components.Add(emitter1);
}

As you can see I have created an emitter with 100 particles in it and placed it 20 units in front of the Camera (see sample download for Camera code)

HLSL

OK, now it's time for the shader. I have adapted this from the shader that can be found in my previous 3D particle sample. It is pretty simple, we have a texture to use for the particle, two matrices for world view projection and projection, and view port height, used to calculate the particles size.

In the other particle sample the shader is written so it can be used on the PC and the XBox 360, as this it a tutorial, I have removed the XBox 360 elements so the shader is easier to read.

I have created one data structure for passing data from the application to the Vertex Shader and one to pass data from the Vertex Shader to the Pixel Shader. As you can see the former matches the structure we created in our PointSpriteElement structure (Now I am still pretty new to this, but you will notice the color element in out application structure is represented by a single float, yet in the shader it is 4, if I set the application structure up so the color is size 4*4 rather 1*4 then I get some odd results...)

So the structures looks like this

struct VS_OUTPUT
{
    half4 Position : POSITION0;
    half2 TexCoord : TEXCOORD0;
    half4 Color : COLOR0;
    half psize : PSIZE0;
};

struct VS_INPUT
{
    half4 pos : POSITION0;
    half4 color : COLOR0;
    half2 data : TEXCOORD0;
};

Our Vertex Shader looks like this

VS_OUTPUT VertexShader(VS_INPUT input)
{
    VS_OUTPUT Output = (VS_OUTPUT)0;
    
    Output.Position = mul(input.pos, WorldViewProj);    
    
    Output.Color = input.color;    
    
    Output.Color.a = input.data.y;                
    
    Output.psize = input.data.x * Projection._m11 / Output.Position.w * ViewportHeight / 2;
    
    return Output;
}

So in here we tell the Vertex Shader to expect out VS_INPUT structure as the incoming parameters and that it is going to return a VS_OUTPUT structure. We create a VS_OUTPUT structure and initalize it setting all the values to 0. We then calculate the position of the vertex in the world and load it into our return structure, then load the color, color alpha and the particle size and finally return the structure to the calling pass.

Our Pixel Shader looks like this

half4 PixelShader(VS_OUTPUT input) : COLOR0
{
    half4 Color = tex2D(Sampler, input.TexCoord.xy);
    Color *= input.Color;   
    return Color;   
}

In this Pixel Shader we tell it to expect a VS_OUITPUT structure as the input from the Vertex Shader and that is will return a half4 and that this half4 is a COLOR. We get the color of the pixel ath this TexCoord, multiply it by the color we want to tint the texture by and then pass it out to the calling pass.

So the complete shader looks like this

texture particleTexture;
sampler Sampler = sampler_state
{
    Texture = <particleTexture>;
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
};

half4x4 WorldViewProj : WorldViewProjection;
half4x4 Projection : Projection;

half ViewportHeight;

struct VS_OUTPUT
{
    half4 Position : POSITION0;
    half2 TexCoord : TEXCOORD0;
    half4 Color : COLOR0;
    half psize : PSIZE0;
};

struct VS_INPUT
{
    half4 pos : POSITION0;
    half4 color : COLOR0;
    half2 data : TEXCOORD0;
};

VS_OUTPUT VertexShader(VS_INPUT input)
{
    VS_OUTPUT Output = (VS_OUTPUT)0;
    
    Output.Position = mul(input.pos, WorldViewProj);    
    
    Output.Color = input.color;    
    
    Output.Color.a = input.data.y;                
    
    Output.psize = input.data.x * Projection._m11 / Output.Position.w * ViewportHeight / 2;
    
    return Output;
}
half4 PixelShader(VS_OUTPUT input) : COLOR0
{
    half4 Color = tex2D(Sampler, input.TexCoord.xy);
    
    Color *= input.Color;    
    
    return Color;    
}
technique PointSpriteTechnique
{
    pass P0
    {   
        vertexShader = compile vs_2_0 VertexShader();
        pixelShader = compile ps_2_0 PixelShader();        
    }
}

I guess that brings this first tutorial to a close, hope you find it of help and has made particles a little clearer. As I say at the top, I am no expert, but this is how I am implementing PointSprite particles and it seems to be doing the job for me.

If you want the example source for this tutorial you can find it here.

Comments

 

GameDevKicks.com said:

You've been kicked (a good thing) - Trackback from GameDevKicks.com

March 26, 2008 2:03 PM
 

Pete said:

> "If you want the example source for this tutorial you can find it here."

lol

March 26, 2008 7:43 PM
 

Nemo Krad said:

... I guess there is a lot of source in the post eh... Well with the download you get the project.

Sheesh some people are never happy...:P

March 26, 2008 8:55 PM
 

A Few New XNA Tutorials : Stromcode said:

Pingback from  A Few New XNA Tutorials : Stromcode

March 30, 2008 4:02 AM
 

Gokz1000 said:

Very nice tutorial on point sprites, much better than the one I used when I was learning about point sprites. Sunspots uses a system that is pretty similar to the one you use in this tutorial but, yours is much better >.<

March 31, 2008 3:50 PM
 

Nemo Krad said:

Well if you think it's better you are more than welcome to it.

I am writing up a billboarding  tutorial now, it all kind of fits in well for when I put the post up on the cloud system.

Sunspots is looking great by the way.

March 31, 2008 7:49 PM
 

UH COSC Interactive Game Development » 3D PointSprite Particle Tutorial I said:

Pingback from  UH COSC Interactive Game Development &raquo; 3D PointSprite Particle Tutorial I

May 21, 2008 4:46 PM
 

UH COSC Interactive Game Development » RandomChaos Particle Tutorial Series said:

Pingback from  UH COSC Interactive Game Development &raquo; RandomChaos Particle Tutorial Series

May 21, 2008 4:59 PM
 

raoyadav said:

all bakwash

May 26, 2008 10:09 AM
 

Nemo Krad said:

eh?

May 26, 2008 7:32 PM

Leave a Comment

(required)  
(optional)
(required)  
Add

About Nemo Krad

Have been a professional developer since 1995. My skill set ranges from C/C++, VB6,MSSQL, Java Script, VB Script, HTML/ASP, Java, C#, ASP.NET as well as others. I started 3D and games development when the first release of XNA came out in December 2006 and have become addicted to it.