Particle Effects on Windows Phone 7
01:12
Posted by Jonathan Yong
Being a complete noob at games programming, I initially approached particle rendering by calling Draw() for each particle. I'm not sure if you know this, but calling Draw() several thousand times per frame results in a slideshow.
So here's my solution to particle explosions. Each particle is represented by two vertices that form a line. As the particle gets older, its velocity decreases, along with its length. This produces a sort of tracer bullet effect.
To avoid multiple Draw() calls, I create a large vertex buffer that holds the total number of vertices that represent all of the particles i.e. particles * 2. Each vertex is translated to the position of the particle before being inserted into this giant vertex buffer.
The resulting vertex buffer can then be rendered using one DrawUserPrimitives() call. If I remember correctly, Shawn Hargraves says that DrawUserPrimitives() is the best way to draw vertex buffers that change frequently i.e. per frame. I also tried using a DynamicVertexBuffer but this seems to be the best way.
In addition to the particle lines, I decided to add some additively blended sprites to give it a more interesting look. The sprite I used is a simple circle with a gradient. To position the sprite I calculate the 2D position from the particle's 3D coordinates using the Graphics.Viewport.Project() method.
You can see the drawing code here below. The code for the creation of particles uses standard object pooling to avoid garbage collection. An object is taken from the pool, given an initial position, direction and velocity and placed into an array of "alive" particles. After a certain time the particle "dies", is removed from the live particles array and is ready to be re-used in the object pool.
You can see a video of the particle effects below. The fireball part of the explosion is a simple textured sphere. As it gets older, its scale increases and its alpha value decreases.
1 public static int MAX_PARTICLES = 2000;
2 VertexPositionColor[] explosionVertices;
3
4 protected void InitParticleVertices()
5 {
6 explosionVertices = new VertexPositionColor[MAX_PARTICLES * 2];
7 }
8
9 public void Draw(GraphicsDevice Graphics, BasicEffect shader)
10 {
11 if (aliveParticles.Count > 0)
12 {
13 int vertexCount = 0;
14 int particleCount = 0;
15 foreach (ExplosionParticle particle in aliveParticles)
16 {
17 Vector3 v1 = Vector3.Zero;
18 Vector3 v2 = new Vector3(0, 0, -1);
19 Vector3 v3;
20 //shorten the particle line based on its age
21 Vector3.Multiply(ref v2, particle.Age, out v3);
22 Vector3.Subtract(ref v2, ref v3, out v2);
23
24 Matrix R = Matrix.CreateRotationY(particle.Angle);
25 Matrix T = Matrix.CreateTranslation(particle.Pos);
26
27 //rotate and position the line
28 Matrix W;
29 Matrix.Multiply(ref R, ref T, out W);
30
31 Vector3.Transform(ref v1, ref W, out v1);
32 Vector3.Transform(ref v2, ref W, out v2);
33
34 explosionVertices[vertexCount].Position = v1;
35 explosionVertices[vertexCount].Color = particle.Color;
36
37 vertexCount++;
38 explosionVertices[vertexCount].Position = v2;
39 explosionVertices[vertexCount].Color = particle.Color;
40
41
42 vertexCount++;
43 particleCount++;
44 }
45
46 BlendState oldBlend = Graphics.BlendState;
47 Graphics.BlendState = Game.additiveBlendState;
48
49 shader.Alpha = 1.0f;
50 shader.LightingEnabled = false;
51 shader.DiffuseColor = Color.White.ToVector3();
52 shader.VertexColorEnabled = true;
53 shader.World = Matrix.Identity;
54
55 //draw particle lines in one Draw() call
56 foreach (EffectPass pass in shader.CurrentTechnique.Passes)
57 {
58 pass.Apply();
59 Graphics.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, explosionVertices, 0, aliveParticles.Count);
60 }
61
62 //draw a sprite for each particle, slightly offset from the particle line
63 Game.SpriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.Additive);
64 for (int x = 0; x < aliveParticles.Count; x++)
65 {
66 ExplosionParticle particle = aliveParticles[x];
67 //use Project() to determine the position in 2D screen space
68 Vector3 screenCoords = Graphics.Viewport.Project(particle.Pos + (2f * particle.dir), shader.Projection, shader.View, Matrix.Identity);
69
70 //As with the line lengths, reduce the size of the sprite based on the age of the particle
71 float size = 8f;
72 size *= (1f - particle.Age);
73
74 Color color = Color.White;
75 if (Utils.GetRandomNumber(0, 2) == 1)
76 {
77 color = particle.Color;
78 }
79
80 Rectangle rect = new Rectangle((int)screenCoords.X, (int)screenCoords.Y, (int)size, (int)size);
81 Game.SpriteBatch.Draw(bloomTexture, rect, color);
82 }
83 Game.SpriteBatch.End();
84
85
86 Graphics.BlendState = oldBlend;
87 shader.Alpha = 1f;
88 shader.VertexColorEnabled = false;
89 }
90 }
24 April 2011 at 14:41
hi can you please upload sample solution for this effect?
23 November 2011 at 21:29
Yeah, it would be really nice if you would share a complete solution :)
I'm just trying to get the right angle,...
9 December 2011 at 22:00
Same here, thumbs up for the source project !!!
Nice job btw !!