OpenGL ES Particle System Tutorial: Part 2/3

In this second part of our OpenGL ES particle system tutorial series, learn how to implement a generic particle system that deals with some “explosive” concepts! By Ricardo Rendon Cepeda.

Leave a rating/review
Save for later
Share

Welcome back to our 3-part OpenGL ES particle system tutorial series! Here you’ll learn how to make a cool and fun particle system from scratch, and integrate it into an iOS app.

Here’s an overview of the series:

  • Part 1: In the first part, you learned all about particle systems and point sprites, and created a small app to help you learn as you go.
  • Part 2: You are here! This time you’ll learn to create a generic particle-emitter paired system. That’s code word for “awesome and reusable”.
  • Part 3: Finally, you’ll use your newly developed skills to integrate particle effects into a simple 2D game.

Without further ado, time to get cracking on your new particle-emitter system!

Getting Started

To get started, you’ll need a simple project that uses OpenGL ES 2.0 and GLKit to render a blue screen that will serve as a starting point.

If you completed Part 1 of this tutorial series like a boss, see if you can complete a similar project setup named GLParticles2 that renders a blue screen (0.53, 0.81, 0.92, 1.00) — without downloading the starter code.

Like A Boss

If you need some help, feel free to have a peek at the basic instructions and requisites hidden below:

[spoiler title=”Challenge Hints”]

  1. Start with the Empty Application template using ARC and name your project GLParticles2.
  2. Add the required frameworks OpenGLES and GLKit.
  3. Create a Storyboard with a GLKit View Controller. Use it to deploy your app in Portrait orientation only.
  4. Create a GLKViewController subclass and name it MainViewController. Attach it to your storyboard.
  5. Insiede MainViewController, implement viewDidLoad to create an EAGLcontext using OpenGL ES 2.0 and attach it to your GLKview. Implement the delegate method glkView:drawInRect to set the view to a blue color (0.53, 0.81, 0.92, 1.00).

[/spoiler]

If you haven’t read Part 1, want to save a bit of time, are itching to get to the explosion part of this tutorial, or just aren’t feeling like a boss today, then feel free to download the starter code for this tutorial to hurry things along.

Close Enough

Whichever option you chose, build and run your project! You should now have an empty blue screen as shown below:

Run1

Designing Your Particle System

In Part 1 you learned that particle systems work in a two-level hierarchy composed of the emitter and the particles themselves. Furthermore, you saw that particle systems are affected by global, external factors such as time.

To put this knowledge to good use, you need to find a good real-world system to model that involves lots of individual particles, moving independently, and affected by external forces. Hmm — how about an explosion? Or more specifically, a simple blast, where your particles will spread outwards from your emitter center over time.

Go to File\New\File… and create a new file with the iOS\Cocoa Touch\Objective-C class subclass template. Enter EmitterObject for the class and NSObject for the subclass. Make sure both checkboxes are unchecked, click Next, and click Create.

Open EmitterObject.h and replace the #import line with the following:

#import <GLKit/GLKit.h>

Still working in EmitterObject.h, replace the contents of EmitterObject.m with:

#import "EmitterObject.h"

#define NUM_PARTICLES 180

typedef struct Particle
{
    float       pID;
    float       pRadiusOffset;
    float       pVelocityOffset;
    float       pDecayOffset;
    float       pSizeOffset;
    GLKVector3  pColorOffset;
}
Particle;

typedef struct Emitter
{
    Particle    eParticles[NUM_PARTICLES];
    float       eRadius;
    float       eVelocity;
    float       eDecay;
    float       eSize;
    GLKVector3  eColor;
}
Emitter;

@implementation EmitterObject
{
    // Instance variables
    GLKVector2  _gravity;
    float       _life;
    float       _time;
}

@end

You may have noticed that this is set up similar to EmitterTemplate.h in Part 1 of this tutorial. However, since you’re creating a generic particle system, the emitter code has now been abstracted into a class in order to easily create multiple emitters.

Additionally, particle and emitter properties have been prefixed with p and e respectively to ease implementation as your project grows.

One important field in Particle is pID. This serves as a unique identifier for each particle. It will store an angle in radians which you’ll use to calculate each particle’s position in the simulation.

From fireworks to demolitions, explosives have many inherent properties calculated at the emitter level. However, due to the individual nature of fragments, they are offset at the particle level. For example, an explosion may have an average blast radius of 10m, but fragments may travel anywhere between 9m or 11m from the emitter source.

In your system design, every emitter property listed below also has a comparable particle offset:

  • Blast radius
  • Explosion velocity
  • Explosion decay
  • Fragment size
  • Fragment color

These properties define a simulation that is further governed by the following global external factors:

  • World gravity
  • Simulation lifetime
  • Current time

Thus you have the basis for the structures, and their respective variables, defined in EmitterObject.m.

Abstracting Your Rendering Cycle

Since each emitter object is unique, each instance should also manage its own set of rendering cycle instructions.

Add the following method declarations to EmitterObject.h, just before the @end line:

- (id)initEmitterObject;
- (void)renderWithProjection:(GLKMatrix4)projectionMatrix;
- (void)updateLifeCycle:(float)timeElapsed;

Add the following basic implementations for these methods to EmitterObject.m, just before the @end line:

- (id)initEmitterObject
{
    if(self = [super init])
    {
        // Initialize variables
        _gravity = GLKVector2Make(0.0f, 0.0f);
        _life = 0.0f;
        _time = 0.0f;
    }
    return self;
}

- (void)renderWithProjection:(GLKMatrix4)projectionMatrix
{
    
}

- (void)updateLifeCycle:(float)timeElapsed
{
    
}

Due to their parameters and public declaration, you may have deduced that these methods are meant to be accessed from outside the class. If so, you’re absolutely right!

initEmitterObject initializes the variable of an EmitterObject instance. Your GLKit view controller calls renderWithProjection: to display the rendered scene properly within your GLKView. Finally, updateLifeCycle: is called for each refresh cycle to give your emitter a chance to update itself appropriately.

Open up MainViewController.m and add the following code to the top of the file, after the #import statement:

#import "EmitterObject.h"

@interface MainViewController ()

// Properties
@property (strong) EmitterObject* emitter;

@end

This gives MainViewController access to an EmitterObject.

Add the following line to MainViewController.m at the end of viewDidLoad:

// Set up Emitter
self.emitter = [[EmitterObject alloc] initEmitterObject];

This creates a new EmitterObject instance and stores it in your new emitter property.

Add the following code to MainViewController.m at the end of glkView:drawInRect::

// Create Projection Matrix
float aspectRatio = view.frame.size.width / view.frame.size.height;
GLKMatrix4 projectionMatrix = GLKMatrix4MakeScale(1.0f, aspectRatio, 1.0f);
    
// Render Emitter
[self.emitter renderWithProjection:projectionMatrix];

This code defines the position of the scene that is visible in the GLKView. Recall from Part 1 of this tutorial series that OpenGL ES 2.0 screen coordinates range from -1 to +1 on the x and y axes. Therefore, this matrix converts between the device’s coordinate system and the GLKView‘s coordinate system (width and height in points).

You then pass this projection matrix to your emitter object’s renderWithProjection: for every GLKView draw cycle.

Finally, add the following method to MainViewController.m just before the @end line at the bottom of the file:

- (void)update
{
    // Update Emitter
    [self.emitter updateLifeCycle:self.timeSinceLastUpdate];
}

In the code above, update is called automatically by GLKit at the appropriate refresh rate. Inside update you call emitter‘s updateLifeCycle: to give your emitter a chance to update itself. You’ll flesh out updateLifeCycle: a bit later.

Now you can handle all of your OpenGL ES 2.0 calls within your EmitterObject class. Pretty slick!

Ricardo Rendon Cepeda

Contributors

Ricardo Rendon Cepeda

Author

Over 300 content creators. Join our team.