XNA UK User Group
A helping hand for bedroom coders throughout the land.
3D Billboard Particles Tutorial VI

Blogs

RandomChaos

Syndication

http://www.youtube.com/v/tnsSfwGfM3U <p><a href="http://www.youtube.com/v/tnsSfwGfM3U">http://www.youtube.com/v/tnsSfwGfM3U</a></p>

So, we have seen PointSprite particles in 3D, but what about billboarding??

You may be asking at this point, what is billboarding and why would I want to use it when I already have a nice PoitSprite system that does the job I want...?

A billboard object is basically a textured quad, again the question "What is a textured quad?" Well this is two triangle primitives used to draw a square and has a texture applied to it. It gets the name quad as it is four points in space rather than three.

As to why use them, well I can only tell you my experiences of the two, first off I go for a PointSprite system pretty much every time when it comes to effects as it tends to be faster and 9 times out of 10 will do all that you need to, but in the case of the cloud system I am doing my best to write, Leaf pointed out that what is described as the "Parting of the Red Sea" effect by Ninian in here paper can be avoided by using billboarding. I assume because the position of a PointSprite is indeed that a single point in space, where as a billboard is a textured quad so has five points, I say five as you have each corner plus the centre and so when passing through the camera does not give that odd parting effect, also I think you are limited to a maximum PointSprite size, this can be altered in the RenderState using PointSizeMax but this too has an upper limit, a billboard does not. So like in the CC sample for billboarding, they can be used for grass and trees or like my cloud system.....clouds. Also, if a point sprite system does all that you need, then don't worry about billboarding.

The main issues you have with creating billboards is keeping them facing the camera in the way you want them to, now you could do this how I used to in my old RandomChaos Engine and orientate each billboard on the CPU, this, as you can imagine is not a very good way of doing things as it just kills your FPS once you have a decent number of particles. The best way, just like in the CC billboard sample is to do it in the shader.

Onto some code, and I will start where I did with the other tutorials with the particle definition, this is pretty much identical to the PointSprite definition, but we do need to add a TEXCOORD field to it, if we don't then we will not be able to apply textures to the billboard correctly. I guess if you really needed it you could also add Tangent data here if you wanted to bumpmap your particles, I have done this in the past, but it looked a little odd, may well have been my lighting algorithms though. Anyway, here is the data structure we are going to use for our billboard particle system

public struct BillboardParticleElement
{
    Vector3 position;
    Vector2 textureCoordinate;
    Color color;
    Vector4 data;

    public Vector3 Position
    {
        get { return position; }
        set { position = value; }
    }
    public Vector2 TextureCoordinate
    {
        get { return textureCoordinate; }
        set { textureCoordinate = value; }
    }
    public Vector4 Data
    {
        get { return extras; }
        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 BillboardParticleElement(Vector3 Position, Vector2 TextureCoordinate, Vector2 XYScale, Color Color)
    {
        position = Position;
        textureCoordinate = TextureCoordinate;
        data = Vector4.One;
        color = Color;
    }
    static public VertexElement[] VertexElements = new VertexElement[]
        {
            new VertexElement(0,0,VertexElementFormat.Vector3,VertexElementMethod.Default,VertexElementUsage.Position,0),
            new VertexElement(0,4*3,VertexElementFormat.Vector2,VertexElementMethod.Default,VertexElementUsage.TextureCoordinate,0),                
            new VertexElement(0,4*5,VertexElementFormat.Color ,VertexElementMethod.Default,VertexElementUsage.Color,0),
            new VertexElement(0,4*6,VertexElementFormat.Vector4,VertexElementMethod.Default,VertexElementUsage.Position,1),
        };

    public static int SizeInBytes = (3 + 2 + 1 + 4) * 4;
}

As you can see there is little difference in this structure and the one used for the PointSprite system. I have added a Vector2 for the TEXCOORD field and so in the Element array I have added an extra element so it can be passed to the Vertex Shader.

Now onto the emitter, this again is almost identical to the one in tutorial I, only this time I am going to set up up ready to be bolted into our game Agent. There area couple of new elements to go into this emitter, we need a DynamicVertexBuffer to store our particle array in, and a DynamicIndexBuffer to store the draw order of the primitives, and a VertexDeclaration, this gives the Graphics Device a definition of our particle structure. All the other fields are pretty much the same as what we have in the previous tutorials, the real difference comes when we are loading, updating and drawing the particles. The emitter  class looks like this

public class ParticleEmitter
{
    private DynamicVertexBuffer vb;
    private DynamicIndexBuffer ib;
    VertexDeclaration m_vDec;

    public BillboardParticleElement[] particleArray;

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

    public Color particleColor;

    public int partCount;

    Effect shader;

    Game game;

    int nextParticle = 0;
    BasicModel targetPos;
    Vector3 myLastpos;

    public ParticleEmitter(Game game, int particleCount,BasicModel model)
    {
        this.game = game;
        myPosition = Vector3.Zero;
        myScale = Vector3.One;
        myRotation = new Quaternion(0, 0, 0, 1);

        partCount = particleCount;
        targetPos = model;

        particleColor = Color.White;
    }

    public void LoadContent()
    {
        m_vDec = new VertexDeclaration(game.GraphicsDevice, BillboardParticleElement.VertexElements);

        shader = game.Content.Load<Effect>("Shaders/BillboardShader");
        shader.Parameters["particleTexture"].SetValue(game.Content.Load<Texture2D>("Textures/smoke"));

        
        myLastpos = targetPos.myPosition;

        LoadParticles();
    }

    private void LoadParticles()
    {
        particleArray = new BillboardParticleElement[partCount * 4];

        for (int p = 0; p < partCount; p+=4)
        {
            for (int thisP = 0; thisP < 4; thisP++)
            {
                int currentParticle = p + thisP;

                particleArray[currentParticle] = new BillboardParticleElement();
                particleArray[currentParticle].Position = myPosition;
                particleArray[currentParticle].Color = particleColor;
                particleArray[currentParticle].Data = new Vector4(1f,1f,0,0);
                switch (thisP)
                {
                    case 0:
                        particleArray[currentParticle].TextureCoordinate = Vector2.Zero;
                        break;
                    case 1:
                        particleArray[currentParticle].TextureCoordinate = new Vector2(1, 0);
                        break;
                    case 2:
                        particleArray[currentParticle].TextureCoordinate = new Vector2(0, 1);
                        break;
                    case 3:
                        particleArray[currentParticle].TextureCoordinate = Vector2.One;
                        break;
                }
            }
        }
                   
        short[] indices = new short[6 * partCount];

        for (int part = 0; part < partCount; part++)
        {
            int off = part * 6;
            int offVal = part * 4;

            indices[off + 0] = (short)(0 + offVal);
            indices[off + 1] = (short)(1 + offVal);
            indices[off + 2] = (short)(2 + offVal);

            indices[off + 3] = (short)(1 + offVal);
            indices[off + 4] = (short)(3 + offVal);
            indices[off + 5] = (short)(2 + offVal);
        }

        ib = new DynamicIndexBuffer(game.GraphicsDevice, typeof(short), 6 * partCount, BufferUsage.WriteOnly);
        ib.SetData(indices);
    }

    public void Update(GameTime gameTime)
    {
        for (int p = 0; p < particleArray.Length; p+= 4)
        {
            for (int thisP = 0; thisP < 4; thisP++)
            {
                if (p == nextParticle && myLastpos != myPosition)
                {
                    particleArray[p + thisP].Position = myLastpos;
                    particleArray[p + thisP].Color = particleColor;
                }

                particleArray[p + thisP].SpriteSize = (Vector3.Distance(particleArray[p + thisP].Position, targetPos.myPosition) / 5);
            }
        }
        nextParticle++;

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

        vb = new DynamicVertexBuffer(game.GraphicsDevice, typeof(BillboardParticleElement), 4 * partCount, BufferUsage.WriteOnly);
        vb.SetData(particleArray);

        myLastpos = targetPos.myPosition;
    }
    public void Draw(GameTime gameTime)
    {
        game.GraphicsDevice.VertexDeclaration = m_vDec;
        game.GraphicsDevice.Vertices[0].SetSource(vb, 0, BillboardParticleElement.SizeInBytes);
        game.GraphicsDevice.Indices = ib;

        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.AlphaBlendEnable = true;
        game.GraphicsDevice.RenderState.SourceBlend = Blend.One;
        game.GraphicsDevice.RenderState.DestinationBlend = Blend.One;

        game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

        Matrix World = Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition);
        shader.Parameters["world"].SetValue(World);
        Matrix vp = Camera.myView * Camera.myProjection;
        shader.Parameters["vp"].SetValue(vp);

        shader.Begin();
        for (int ps = 0; ps < shader.CurrentTechnique.Passes.Count; ps++)
        {
            shader.CurrentTechnique.Passes[ps].Begin();
            game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4 * partCount, 0, partCount * 2);
            shader.CurrentTechnique.Passes[ps].End();
        }
        shader.End();

        game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

        game.GraphicsDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;

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

    }
}

The first obvious change is in the LoadContent method, other than the assets beign from different files at the top we set up the VertexDeclarartion. LoadParticles is TOTALY different. This is because we have to set up a billboard per particle, so at the top we have to set our array to the number of particles we want * 4 as each bill board has 4 corners, then we have  the main loop that go through each particle and in that a second loop that moves us through each point of that particle.

For the given array element we create a new particle instance, the position is set to that of the emitter for each corner, don't worry about this as the billboards size will be set in the shader. We set the color and the data of the particle setting the scale and alpha (unsing additive blending in this example so alpha wont be used).

We then need to set the TEXCOORD of this corner, as I am sure you know TEXTCOORDS range from 0,0 which is the top left corner to 1,1 which is the bottom right corner. So if I had a texture that was 100 x 100 pixels and I wanted to get the pixel in the very centre (well just off centre) it would be located at .5,.5 this would get me pixel 50,50 off the image.

We then set up the draw order of our billboard corners in the DynamicIndexBuffer, you will see I am using a short[] array to store this data, this is because I have a very poor Graphics Card that can't handle 32 bit index buffers, if you have a good card, swap these types for an int. You will also notice that we have 6 indices per particle, this is because the bill board is made up from 2 triangles.

The Update method is really the same as before only we have to jump 4 elements per particle as we now have 4 elements per particle. The major change here is the addition of setting the DynamicVertexBuffer

The Draw method again has few changes, at the top we set the VertexDeclaration, bind the vertex buffer and index buffer to the device, setup our blending method and shader, send the particles to the device and reset our RenderStates.

So as you can see the major difference is setting up the Graphics device to use our particle array and the fact that our particles are now made from 4 corners.

In this tutorial I have gone strait to binding the emitter to a game agent, for simplicity I have taken the BasicModel class I did for my A.I. Finite State Machine sample and removed the A.I. from it, added an instance of our emitter and that was pretty much it. The BasicModel class now looks like this

public class BasicModel : DrawableGameComponent
{
    private Model Mesh;
    public Vector3 myPosition;
    public Quaternion myRotation;
    public Vector3 myScale;

    ParticleEmitter emitter;

    Effect shader;

    string modelAsset;
    string shaderAsset;

    Vector4 AmbientColor = Color.Brown.ToVector4();

    public Vector3 velocity = Vector3.Zero;
    public float speed = .01f;

    public BasicModel(Game game, string modelAsset, string shaderAsset) :base(game) 
    {
        this.modelAsset = modelAsset;
        this.shaderAsset = shaderAsset;

        myPosition = new Vector3(0, 0, -1);
        myRotation = new Quaternion(0, 0, 0, 1);
        float scale = .5f;
        myScale = new Vector3(scale, scale, scale);

        emitter = new ParticleEmitter(game, 200, this);

    }
    public override void Update(GameTime gameTime)
    {
        velocity = new Vector3((float)Math.Cos(gameTime.TotalGameTime.TotalSeconds) * 20, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 20, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 30);
        Move();
        emitter.Update(gameTime);

        base.Update(gameTime);
    }
    protected override void LoadContent()
    {
        emitter.LoadContent();
        Mesh = Game.Content.Load<Model>(modelAsset);
        shader = Game.Content.Load<Effect>(shaderAsset);

        base.LoadContent();
    }
    public override void Draw(GameTime gameTime)
    {
        Matrix World = Matrix.CreateScale(myScale) *
                        Matrix.CreateFromQuaternion(myRotation) *
                        Matrix.CreateTranslation(myPosition);

        shader.Parameters["World"].SetValue(World);
        shader.Parameters["View"].SetValue(Camera.myView);
        shader.Parameters["Projection"].SetValue(Camera.myProjection);

        shader.Parameters["EyePosition"].SetValue(Camera.myPosition);

        shader.Parameters["AmbientLightColor"].SetValue(AmbientColor);
        shader.Parameters["DiffuseColor"].SetValue(Color.Aquamarine.ToVector4());
        shader.Parameters["LightDiffuseColor"].SetValue(Color.SteelBlue.ToVector4());
        shader.Parameters["SpecularPower"].SetValue(16);
        shader.Parameters["LightSpecularColor"].SetValue(Color.Silver.ToVector4());

        shader.Parameters["LightPosition"].SetValue(new Vector3(-10, 10, 0));

        for (int pass = 0; pass < shader.CurrentTechnique.Passes.Count; pass++)
        {
            for (int msh = 0; msh < Mesh.Meshes.Count; msh++)
            {
                ModelMesh mesh = Mesh.Meshes[msh];
                for (int prt = 0; prt < mesh.MeshParts.Count; prt++)
                    mesh.MeshParts[prt].Effect = shader;
                mesh.Draw();
            }
        }

        emitter.Draw(gameTime);
        base.Draw(gameTime);
    }

    public virtual void Translate(Vector3 distance)
    {
        myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(new Quaternion(0, 0, 0, 1)));
    }
    public virtual void Rotate(Vector3 axis, float angle)
    {
        axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
        myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);
    }
    public void LookAt(Vector3 target, float speed)
    {
        Vector3 tminusp = target - myPosition;
        Vector3 ominusp = Vector3.Backward;

        tminusp.Normalize();

        float theta = (float)System.Math.Acos(Vector3.Dot(tminusp, ominusp));
        Vector3 cross = Vector3.Cross(ominusp, tminusp);

        cross.Normalize();

        Quaternion targetQ = Quaternion.CreateFromAxisAngle(cross, theta);
        myRotation = Quaternion.Slerp(myRotation, targetQ, speed);
    }

    private void Move()
    {
        // Calulate the distance I need to travel.
        Vector3 distance = velocity * speed;

        // Find out my direction of facing as I move.
        Vector3 target = Vector3.Transform(distance, Matrix.CreateFromQuaternion(new Quaternion(0, 0, 0, 1)));
        target += myPosition;
        LookAt(target, .1f);

        // Move.
        Translate(distance);

    }
}

All we need to do now is add our agent to the Game.Components collection and away we go.

HLSL

OK now as you can see in all that code, not once do we try and make the billboard face the camera, this is all done in the shader. This is how I do it.

First of all I get me world UP vector, this tells me what orientation is the UP vector, I do this by getting the cross product of -1,0,0 and 0,0,-1, I then need the eye vector of the viewer and I can get this from the View elements of the ViewProjection Matrix like this vp._m02_m12_m22, I can then get the side and up vector of the billboard orientation by getting the normalized cross produce of the eyeVector and the world up vector. I can then apply this to the final position of my Vertex. This is done in two steps, the first manages the sideVector orientation, I subtract .5 from the current TEXCOORD.x multiply that by the sideVector and the scale I want the particle to be, the second step I subtract the current  TEXCCORD.y from .5 and multiply that by the upVector and the scale I want the particle to be. As you can see with that methods you can scale both the X and Y dimensions of your billboard differently.

Why am I subtracting .5 from the TEXCOORD in the first stage and the TEXCOORD from .5 in the second stage. Well, as I said before .5 puts us in the centre of our billboard so for the side orientation the left most point with be at x:0 so giving us -.5 to add to our particle position, this will give us a point in space .5 to the left of the particle, the right most will be x:1 so this will give us the right most point from the centre in space. In the case of the second stage, subtracting .5 from the TEXCOORD puts us at -.5  above the centre, and from the other extream it puts us .5 bellow that centre.

The rest of the shader is pretty much as before, other than the extra handling TEXCOORDS

half4x4 world : World;
half4x4 vp : ViewProjection;

#define worldUp cross(half3(-1,0,0),half3(0,0,-1))

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

struct VertexIn
{
    half4 Position       : POSITION0;             
    half2 TextureCoords: TEXCOORD0;    
    half4 Color        : COLOR0;
    half4 Data : POSITION1;
};
struct VertexOut
{
    half4 Position       : POSITION0;      
    half2 TextureCoords: TEXCOORD0;
    half4  Color        : COLOR0;    
};

struct PixelToFrame
{
    half4 Color : COLOR0;
};
VertexOut VS(VertexIn input)
{
    VertexOut Out = (VertexOut)0;
    
    half3 center = mul(input.Position,world);    
    half3 eyeVector = vp._m02_m12_m22;
    
    half3 finalPos = center;
    half3 side;
    half3 up;
    
    side = normalize(cross(eyeVector,worldUp));    
    up = normalize(cross(side,eyeVector));        
    
    finalPos += (input.TextureCoords.x - 0.5) * side * input.Data.x;
    finalPos += (0.5 - input.TextureCoords.y) * up * input.Data.x;
    
    half4 finalPos4 = half4(finalPos,1);    
    
    Out.Position = mul(finalPos4,vp);
    Out.TextureCoords = input.TextureCoords;
    
    Out.Color = input.Color;    
    
    // Alpha
    Out.Color.a = input.Data.y;
    
    return Out;
}

PixelToFrame PS(VertexOut input)
{
    PixelToFrame Out = (PixelToFrame)0;
    
    half2 texCoord;
    
    texCoord = input.TextureCoords.xy;    
    
    half4 color = tex2D(partTextureSampler,texCoord);
    
    Out.Color = color * input.Color;    
    
    return Out;
}

technique Go
{
    pass P0 
    {
        VertexShader = compile vs_2_0 VS();
        PixelShader  = compile ps_2_0 PS();
    }
}

Here is another example of billboards being used, the code and sahders are a little more work than shown here but it is pretty much the same technique.

http://www.youtube.com/v/uMvjai9ggzI <p><a href="http://www.youtube.com/v/uMvjai9ggzI">http://www.youtube.com/v/uMvjai9ggzI</a></p>

In the sample source I'm using the smoke texture provided by MS, and the biplane model that comes with the DirectX SDK. You can download the solution project here.


Posted Tue, Apr 1 2008 11:17 AM by Nemo Krad

Comments

GameDevKicks.com wrote 3D Billboard Particles Tutorial VI
on Tue, Apr 1 2008 3:20 PM

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

Kyle Hayward wrote re: 3D Billboard Particles Tutorial VI
on Wed, Apr 2 2008 11:18 PM

Need some billboards of your billboards :P  Clouds are looking good.

Kyle Hayward wrote re: 3D Billboard Particles Tutorial VI
on Sun, Apr 20 2008 4:59 PM

Also, what do you use to get syntax highlighting for your source code? Or does this site have build-in support?

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Apr 21 2008 11:20 AM

I use Windows Live Writer, with a "Paste from Visual Studio" plug in.

Kyle Hayward wrote re: 3D Billboard Particles Tutorial VI
on Tue, Apr 22 2008 1:35 AM

Thanks, that's pretty nice tool.

UH COSC Interactive Game Development » RandomChaos Particle Tutorial Series wrote UH COSC Interactive Game Development &raquo; RandomChaos Particle Tutorial Series
on Wed, May 21 2008 4:59 PM

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

Alex Hogan wrote re: 3D Billboard Particles Tutorial VI
on Mon, Jun 23 2008 5:12 AM

Awesome article - works like a charm, and it's really helping me out.  One question though - and pardon me if I'm being dense:

When drawing the particles I see that you set the GraphicsDevice's Vertex stream source to be the dynamic vertex buffer:

game.GraphicsDevice.Vertices[0].SetSource(vb, 0, BillboardParticleElement.SizeInBytes);

And I see that the data of the DynamicVertexBuffer is set to be the array of your custom vertex element definition, the array of BillboardParticleElement structs:

vb.SetData(particleArray);

I don't understand how the device binds the data in the position, textureCoordinate, color, and data values into the channels/semantics in the shader.  I do see the VertexElement[] definition, but is there any place that specifically binds the data to the struct members?  Is this just an accepted DirectX naming scheme (for the struct Properties?)

Forgive me if I am missing something very obvious here - I am a little new to DirectX and XNA - I'm coming from a 3d art background.

Thanks very much!

Alex

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Jun 23 2008 11:12 AM

OK, I think what you are asking is how is each particle's data set and passed to the shader.

There is an array of BillboardParticleElement, this is the individual particle data and this is what gets passed to the shader as defined in the VertexElement. This maps to the structure in the shader. So the VertexElemets array of VertexElement is how the data is bound as it describes the BillboardParticleElement class. As you will see the data in this structure is defined in the same order the elements are set in the VertexElements array.

Alex Hogan wrote re: 3D Billboard Particles Tutorial VI
on Mon, Jun 23 2008 9:46 PM

Thanks - that clears up some things.  I tried re-ordering the members of the struct to see if that messes things up - and it sure does.

I was expecting an explicit mapping somewhere - but I see that it is reading  x-number of bytes off the front of the struct, defined by the VertexElements[] array.

Super helpful!  And fills a cavity in my XNA understanding.

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Jun 23 2008 10:51 PM

Well it knows how mcuh to read as you specify the structure size in the SizeInBytes field in the BillboardParticleElement and is passed when setting the vertex buffer in the graphics device draw call (game.GraphicsDevice.Vertices[0].SetSource(vb, 0, BillboardParticleElement.SizeInBytes);).

mmarklar wrote re: 3D Billboard Particles Tutorial VI
on Sun, Sep 7 2008 1:21 PM

Hi, thank you for the sample.

When I run it for a while though, it gives me an OutOfVideoMemory exception. It takes a while to happen at 200 particles, but setting a higher particle count should let you reproduce it faster. It seems to be due to creating a new vertex buffer each update:

vb = new DynamicVertexBuffer(game.GraphicsDevice, typeof(BillboardParticleElement), 4 * partCount, BufferUsage.WriteOnly);

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Sep 8 2008 10:04 AM

OK, I think I know what this is and I have had it before in other dynamic particle systems I have written. What I think is happening is the draw call is occurring while the particleArray is being built. I get around this by copying the array to a new array, work on that and once all changes have been made dump it back into the original array.  You can also add a method to ContentLost event that will re bind the original array to the DynamicVertexBuffer.

This was just a sample, don't know if you follow my posts but I going to be posting a particle system soon that should be a bit more robust.

[EDIT]

Having just read back over your post, I don't think this is the issue. I have put the sample up to 20,000 particles and it still does not give me the error you are getting. Are you just playing the sample, or have yo merged it with other work??

[/EDIT]

mmarklar wrote re: 3D Billboard Particles Tutorial VI
on Mon, Sep 8 2008 5:30 PM

I had both played with just the sample, and added in a frame rate counter component. If you can get 20,000 particles, it would seem like I messed up something, since it would crash after 30-60 seconds on an ATI 4850 with 5,000 particles.  I'll try to look at it again in the next few days and let you know if it was something I did.

I actually just found your blog through this article, but I am definitely going to look at your other articles

Thanks again for the sample.

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Sep 8 2008 7:46 PM

That is odd, I have had 20K particles with this sample running for over 5 minutes and no crash.. My graphics card is totaly under powered compared to yours. Maybe I am doing somthing that is upsetting your card...

If you like, zip up you code (without the bin and obj folders) and I will have a look and see if I can find the issue on my system if you like. PM me a link to the zip or your email address.

Petr wrote re: 3D Billboard Particles Tutorial VI
on Fri, Jul 10 2009 5:22 PM

Nice article, but I dont quite understand the last code snipset. I doesnt look like C# code for me...

Nemo Krad wrote re: 3D Billboard Particles Tutorial VI
on Mon, Jul 27 2009 1:35 PM

Sorry for the late reply, I have been on holiday :D

That last code snippet is the shader code, the code that goes in the .fx file. It's not C#