2

I am developing a game for iPhone using OpenGL ES 1.1. In this game, I have blood particles which emit from characters when they are shot, so there can be 1000+ blood particles on the screen at any one time. The problem is that when I have over 500 particles to render, the game's frame rate drops immensely.

Currently, each particle renders itself using glDrawArrays(..), and I know this is the cause for the slow down. All particles share the same texture atlas.

So what is the best option to reduce slow down from drawing many particles? Here are the options I found:

  1. group all the blood particles together and render them using a single glDrawArrays(..) call --if I use this method, is there a way for each particle to have its own rotation and alpha? Or do all of them HAVE to have the same rotation when this method is used? If I can't render particles with unique rotation, then I cannot use this option.
  2. Use point sprites in OpenGL ES 2.0. I am not using OpenGL ES 2.0 yet b/c I need to meet a deadline which I have set to release my game on the App Store. To use OpenGL ES would require preliminary research which unfortunately I do not have the time to perform. I will upgrade to OpenGL ES 2.0 upon a later release, but for the first, I only want to use 1.1.

Here is each particle rendering itself. This is my original particle-rendering methodolgy which caused the game to experience a significant drop in frame rate after 500+ particles were being rendered.

// original method: each particle renders itself.
// slow when many particles must be rendered

[[AtlasLibrary sharedAtlasLibrary] ensureContainingTextureAtlasIsBoundInOpenGLES:self.containingAtlasKey];

glPushMatrix();

// translate
glTranslatef(translation.x, translation.y, translation.z);

// rotate
glRotatef(rotation.x, 1, 0, 0);
glRotatef(rotation.y, 0, 1, 0);
glRotatef(rotation.z, 0, 0, 1);

// scale
glScalef(scale.x, scale.y, scale.z);

// alpha
glColor4f(1.0, 1.0, 1.0, alpha);

// load vertices
glVertexPointer(2, GL_FLOAT, 0, texturedQuad.vertices);
glEnableClientState(GL_VERTEX_ARRAY);

// load uv coordinates for texture
glTexCoordPointer(2, GL_FLOAT, 0, texturedQuad.textureCoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// render
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glPopMatrix();

Then I used method 1, but particles can't have unique rotation, scale, or alpha using this method (that I know of).

    // this is method 1: group all particles and call glDrawArrays(..) once

    // declare vertex and uv-coordinate arrays
    int numParticles = 2000;
    CGFloat *vertices = (CGFloat *) malloc(2 * 6 * numParticles * sizeof(CGFloat));
    CGFloat *uvCoordinates = (CGFloat *) malloc (2 * 6 * numParticles * sizeof(CGFloat));

    ...build vertex arrays based on particle vertices and uv-coordinates.
    ...this part works fine.


    // get ready to render the particles
    glPushMatrix();
    glLoadIdentity();

    // if the particles' texture atlas is not already bound in OpenGL ES, then bind it
    [[AtlasLibrary sharedAtlasLibrary] ensureContainingTextureAtlasIsBoundInOpenGLES:((Particle *)[particles objectAtIndex:0]).containingAtlasKey];

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glTexCoordPointer(2, GL_FLOAT, 0, uvCoordinates);

    // render
    glDrawArrays(GL_TRIANGLES, 0, vertexIndex);

    glPopMatrix();

I'll reiterate my question:
How do I render 1000+ particles without frame rate drastically dropping and each particle can still have unique rotation, alpha, and scale?

Any constructive advice would really help and would be greatly appreciated!

Thanks!

BigSauce
  • 1,830
  • 3
  • 21
  • 27

2 Answers2

3

Use about 1- 10 textures, each made of say 200 red blood dots on a transparent background, and then draw them each about 3 - 10 times. Then you have your thousands of dots. You draw all the images in a spherical pattern, etc - exploding in layers.

You can't always do a 1 to 1 correspondence with reality when gaming. Take a real close look at some games that run on old Xbox or iPad, etc - there are shortcuts you need to do - and they often look great when done.

Tom Andersen
  • 7,132
  • 3
  • 38
  • 55
  • I understand that. However, the issue I'm having is related to the amount of times that I am calling **glDrawArrays**. The more and more I call that method in one rendering of the game, the slower the frame rate gets. Therefore I need a way to more quickly draw each particle. They are already in atlases. It sounds like what you are describing is bunching up particles to act in groups. The problem with that, however, is that particles don't group with each other. They for the most part act in an independent nature. So I need the particles to maintain their independence from one another. – BigSauce Sep 15 '11 at 21:48
  • Good input, though! Anyone else have any suggestions? – BigSauce Sep 15 '11 at 21:51
  • 2
    You mean to say that the blood particles that are emitted on a shot are so important that they have to be individually calculated and rendered? You need to look at explosions and the like in other games with an yes to 'what is the minimum number of polygons i could draw that scene with?'. – Tom Andersen Sep 15 '11 at 23:21
  • If you have a texture with a hundred red dots, then expand it as it moves out from an impact, it will look like 'independent dots'. If your engine already has 1000's of these particles, then it looks like you need to redo the engine too. See http://www.youtube.com/watch?v=DY4hDZ1HjIs for instance. – Tom Andersen Sep 15 '11 at 23:24
  • +1 for Tom's answer(s). I think you may be asking too much of OpenGL ES 1 on an iOS device, and you may have to settle for some workarounds. – Mike Lorenz Sep 16 '11 at 01:04
  • Tom, the blood particles don't emit with an explosive force so as to exit the screen. We're talking about a blood burst from the back of the character when shot from the front. The blood particles are subject to gravity, fall to the ground, and fade out. This way, the user can see and experience the persisting gore. :) – BigSauce Sep 16 '11 at 04:25
  • Tom, could you please further explain what you meant by drawing all of the images in a spherical pattern? I think this may at least be worth a try for other "explosive" particle animations. – BigSauce Sep 16 '11 at 04:28
1

There is significant overhead with each OpenGL ES API call, so it's not a surprise that you're seeing a slowdown here with hundreds of passes through that drawing loop. It's not just glDrawArrays() that will get you here, but the individual glTranslatef(), glRotatef(), glScalef(), and glColorf() calls as well. glDrawArrays() may appear to be the hotspot, due to the way that deferred rendering works on these GPUs, but those other calls will also hurt you.

You should group these particle vertices together in one array (preferably a VBO so that you can take advantage of streaming updated data to the GPU more efficiently). You definitely can replicate the effects of individual rotation, scale, etc. in your combined vertex array, but you're going to need to perform the calculations as to where the vertices should be as they are rotated, scaled, etc. This will place some burden on the CPU for every frame, but that could be offset a bit by using the Accelerate framework to do some vector processing of this.

Color and alpha can be provided per-vertex in an array as well, so you can control that for each one of your particles.

However, I think you're right in that OpenGL ES 2.0 could provide an even better solution for this by letting you write a custom shader program. You could send static vertices in a VBO for all your points, then only have to update matrices to manipulate each particle and the alpha values for each particle vertex. I do something similar to generate procedural impostors as stand-ins for spheres. I describe this process here, and you can download the source code to the application here.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • That is very good feedback, Brad. Thank you for taking the time to provide such a great answer. I tried commenting out the glDrawArrays() code, and there was no slow down at all. I wonder if that's because I'm correct about glDrawArrays() taking so much time when called hundreds of times or just that OpenGL ES knows that there wasn't a glDrawArrays() invoked, so it optimizes and avoids overhead from the glScalef(), glRotatef(), and glTranslatef() methods. In any case, for this first release, I may just use what I have or even try the grouping strategy. – BigSauce Sep 16 '11 at 04:19
  • Brad, could you please provide an example which illustrates the concepts you mentioned? – BigSauce Oct 24 '11 at 04:05
  • @TrueLifeCoder - Which concepts do you mean? The Molecules application I link to above (for which the source code is available) uses VBOs and precalculated locations for the atoms in the OpenGL ES 1.1 side, and custom vertex and fragment shaders for displacing vertices passed in at the center of atoms and rastering in the contents. While more complex than what you're looking for here, the base concepts are the same. – Brad Larson Oct 24 '11 at 15:24
  • Actually, I do understand what you mean, now that I've had time to think about it. Thanks a lot! Now, I am trying the VBO method, but I am having difficulty getting anything to draw. Could you please help me out? My problem is described here [link](http://stackoverflow.com/questions/8554257/opengl-es-1-1-vertex-buffer-object-not-working) – BigSauce Dec 20 '11 at 22:51
  • you said, "You definitely can replicate the effects of individual rotation, scale, etc. in your combined vertex array, but you're going to need to perform the calculations as to where the vertices should be as they are rotated, scaled, etc." Could you please offer an example of how I might accomplish this? This seems to be the easiest option for me given my current framework. Thanks! – BigSauce Dec 29 '12 at 04:24