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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
OpenGL ES Particle System Tutorial: Part 2/3
45 mins
- Getting Started
- Designing Your Particle System
- Abstracting Your Rendering Cycle
- Adding Vertex and Fragment Shaders
- Building an Objective-C Bridge
- Loading Your Shader Program and Particle System
- Rendering Your Explosion
- Enhancing Your Particle System
- Adding Textures
- Adding Multiple Emitters
- Correcting the Rendering Cycle
- Where To Go From Here?
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.
If you need some help, feel free to have a peek at the basic instructions and requisites hidden below:
[spoiler title=”Challenge Hints”]
- Start with the Empty Application template using ARC and name your project GLParticles2.
- Add the required frameworks OpenGLES and GLKit.
- Create a Storyboard with a GLKit View Controller. Use it to deploy your app in Portrait orientation only.
- Create a GLKViewController subclass and name it MainViewController. Attach it to your storyboard.
- Insiede MainViewController, implement
viewDidLoad
to create anEAGLcontext
using OpenGL ES 2.0 and attach it to your GLKview. Implement the delegate methodglkView: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.
Whichever option you chose, build and run your project! You should now have an empty blue screen as shown below:
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!