Posts Tagged particles

GPU accelerated particles

During this last week, some of the work I did was to optimize my particle system, since it was showing up consistently on my profiles and I was adding more and more particles to my environments.

There are a few basic approaches you can take when trying to optimize code like my particle system.

  • The fact that you have a large number of particles all behaving in the same way means that you can easily distribute the work of updating/rendering particles across multiple cores, as long as your data structures and libraries are set up correctly to handle it – so one option is to multithread your particle system.
  • The parallel-friendly nature of a particle system also means that it’s possible to offload much of the work involved in rendering particles directly to the GPU, and do it in a shader instead of on the CPU. This is almost always faster.
  • In fact, in many cases, you can even update your particles on the GPU, by storing their state in a texture or vertex buffer and having a shader run over all the particles and write their new state to another texture/buffer. You can then take the new state and feed it into another shader as input to render your particles.
  • And of course, you can always take the standard approach of brute-force optimization, by making your particle system as efficient as possible with the same basic algorithm.

Read the rest of this entry »

Tags: , , , ,

Platforms, particles and precision

Not a whole lot interesting to show for this week; mostly some performance adjustments and tuning changes.

I spent a day or so wrestling with some weird floating-point accuracy issues. In one case, a routine I was using to convert a convex polygon into a list of triangles wasn’t working correctly in Release builds of the game – rounding errors resulted in a point being represented as outside a triangle when it was actually right on the edge, which ended up causing the routine to iterate forever. (Whoops.)

After a little experimenting, I ended up changing all the variables in the point-in-triangle check routine to use double precision floats, which fixed my test case. Unfortunately, I’m still pretty sure that the routine is broken; doubling the precision just reduced the magnitude of the error. This is particularly annoying since I have had trouble with rounding errors in my collision detection routines in the past, and I still haven’t found a robust approach for deailng with those either. I suspect I’m going to have to spend a while reading up on techniques for reducing error. One upside is that I haven’t seen any rounding errors while running the game on the 360, so if all goes well I won’t ever have to debug PowerPC floating point error issues on my x86 development machine – my understanding is that PPC’s instruction set for floating point arithmetic is far less sensitive than the x86’s, so I’m hoping it won’t be a problem.

I also did a little bit of experimenting with improving the quality of particle effects in the game. I recently added a little effect that causes one-way barriers to emit sparks when their passable axis changes, so that the player’s attention is drawn to the change to avoid confusion in puzzles that rely on barriers. It was pretty, but it also made it obvious that sparks were flying through walls and other geometry, which looked kind of ridiculous. So, in order to address that, I did some work on making sparks not spawn inside of geometry, and making them able to bounce off walls. The end result looked a lot more convincing, but had some unfortunate performance consequences I haven’t managed to get a handle on yet – I spent an hour or two trying to optimize the code responsible for bouncing sparks off walls, and basically got no performance improvement out of it whatsoever; I suspect the profiling I did generated incorrect data for some reason.

Implementing support for sparks bouncing off walls involved some changes to the UpdateSpark routine, mostly the addition of an obstruction test using a new type of obstruction resolver:

        protected bool UpdateSpark (ref SparkParticle particle, int index) {
            particle.Opacity -= 0.01f;

            _ParticleResolver.position = particle.Position;
            _ParticleResolver.velocity = particle.Velocity;
            _ParticleResolver.Reset();
            var result = ObstructionTest(_ParticleResolver, ObstructionFlags.Geometry);
            particle.Position = _ParticleResolver.resultPosition;
            particle.Velocity = _ParticleResolver.resultVelocity;
            if (result == null) {
                particle.Velocity.Y += 0.025f;
            }
            particle.Rotation = (float)(Math.Atan2(particle.Velocity.Y, particle.Velocity.X) + (Math.PI / 2.0f));

            return (particle.Opacity <= 0.0f);
        }

Fairly simple in concept; perform an obstruction test between the particle and nearby level geometry, and use that to determine the new position of the particle. The new resolver looks like this:

    internal class ParticleMotionResolver : ICollisionVisitor {
        public Vector2 position, velocity, resultPosition, resultVelocity;

        public void Reset () {
            resultPosition = position + velocity;
            resultVelocity = velocity;
        }

        public Bounds GetBounds () {
            return new Bounds(position, resultPosition);
        }

        public CollisionState VisitPolygon (Polygon poly) {
            Vector2 intersection;
            bool result = false;

            for (int i = 0; i < poly.Count; i++) {
                var edge = poly.GetEdge(i);
                var normal = Vector2.Normalize(edge.End - edge.Start).Perpendicular();

                if (Geometry.DoLinesIntersect(position, resultPosition, edge.Start, edge.End, out intersection)) {
                    result = true;
                    velocity -= (intersection - position);
                    position = intersection;
                    Vector2.Reflect(ref resultVelocity, ref normal, out resultVelocity);
                    velocity = Vector2.Normalize(resultVelocity) * velocity.Length();
                    resultPosition = position + velocity;
                    if (velocity.LengthSquared() <= 0)
                        return new CollisionState(true, true);
                }
            }

            return result;
        }

        public CollisionState VisitCollidable (ICollidable obj) {
            if (velocity.LengthSquared() <= 0)
                return new CollisionState(true, true);
            else
                return CollisionState.True;
        }
    }

Fairly straightforward, really: For every polygon within the particle’s bounding box, I test the particle’s velocity vector against the polygon’s edges, to see if the particle is going to intersect the edge. If it is, I move the particle so that it’s lying directly on the edge, and then compute a reflection vector so that I know which direction it’s going to bounce in. After that, I apply the particle’s remaining velocity (if any) to bounce it off the edge, and change its direction.

This new routine has the unfortunate side effect of revealing some of the rounding errors lurking in my code – sparks are able to slip through some curved surfaces since the triangles that make up their edges are so small. The end result was definitely an improvement regardless, though.

The last significant thing I worked on was getting moving platforms working correctly again. While moving platforms were one of the first things I implemented originally, A few weeks back I ended up taking them out because they didn’t make sense with my current collision detection model, and they were a constant hassle. Now that my collision detection is more sane, it was easy to adapt the existing code for doors to create a moving platform class that behaves more or less the way I want: You can grip onto the edges of moving platforms just like ledges without falling off; you can climb up onto them; you can ride them; etc. I had to do some tweaking here and there to ensure that you couldn’t ride moving platforms through geometry that’s supposed to obstruct you, and there are still some usability issues – right now the only way to control a platform’s movement is by writing complex vector expressions, which isn’t fun at all; good moving platforms really require a system for defining paths for them to follow, and that’s still on the todo list. Regardless, it’s nice to have platforms working again, since they’re an essential part of my arsenal for designing good puzzles.

Tags: , , ,

Simple Particle Effects

After doing some more tuning on the grappling hook, I decided to take a break and work on a particle system implementation.

To handle various types of particle effects, I made a generic ParticleSystem class, where you can specify a particle type for the system to handle when creating it, along with a function to handle updating the particle, and a texture to use for drawing it:

public class ParticleSystem<T> where T : struct, IParticle {
    public delegate bool Updater (ref T particle, int index);
SmokeParticles = new ParticleSystem<BasicParticle>(SmokeTexture, UpdateSmoke);
SparkParticles = new ParticleSystem<SparkParticle>(SparkTexture, UpdateSpark);

The IParticle interface contains all the properties the particle system needs to draw a particle. So, at runtime, to update and draw each particle system, I just call the appropriate methods:

SmokeParticles.Update();

SpriteBatch.Begin(SpriteBlendMode.AlphaBlend);
SmokeParticles.Draw(SpriteBatch, ViewportPosition);
SpriteBatch.End();

The updater function is responsible for updating every particle each frame, and also determines when it’s time for a particle to die and be removed.

    protected bool UpdateSmoke (ref BasicParticle particle, int index) {
        particle.Scale += 0.05f;
        particle.Rotation += 0.025f;
        particle.Opacity -= 0.01f;

        return (particle.Opacity <= 0.0f);
    }

In my case, I made a couple assumptions that allow me to simplify things some:

  • Particles don’t ever interact with each other.
  • Particles are not modified from outside the particle system after being created.

As a result, I was able to make this optimization (among others):

To remove a ‘dead’ particle, instead of having to use a potentially expensive method call like List.Remove or having to use a garbage-heavy data structure like a linked list, I can instead swap dead particles with the last live particle. Doing this moves the live particle forward in the list so that it will still get updated, and means that the last item in the list is now a dead particle – so all I have to do is reduce the particle count by one.

To integrate the particle system with the game, I wrote helper functions for ’spawning’ particle effects at a given location, like this:

    public void SpawnSparks (Vector2 position) {
        var rng = new Random();
        Vector2 dir;

        for (int i = 0; i < 24; i++) {
            dir.X = rng.NextFloat(-1, 1);
            dir.Y = rng.NextFloat(-1, 1);
            dir.Normalize();

            SparkParticles.Spawn(new SparkParticle {
                Position = position,
                Opacity = rng.NextFloat(0.40f, 0.60f),
                Velocity = dir * rng.NextFloat(1.66f, 2.66f),
                Color = new Color(rng.NextFloat(0.8f, 1.0f), rng.NextFloat(0.45f, 0.65f), rng.NextFloat(0.2f, 0.35f), 1.0f),
                Scale = 0.35f
            });
        }
    }

This function does all the work of spawning a bunch of particles at the right location with randomized parameters, and the updater function does the work after that. The final bit of code goes in the grappling hook class:

    this.Stopped = true;
    _Game.SpawnSparks(this.Position);

The call to SpawnSparks spawns some spark particles at the location of the grappling hook when it first makes contact with a surface and stops.

After I got it working, I used it to add a few simple particle effects to go with some of the game mechanics already in the prototype. It definitely looks a bit nicer, and helps add a little more feedback for the player when doing things like performing a wall-jump.

Tags: , , , ,

luminance is Digg proof thanks to caching by WP Super Cache