OpenGL ES - How to Batch Render 500+ particles w/ different alphas, rotations, and scales?

3.4k views Asked by At

I am developing an iOS game that will need to render 500-800 particles at a time. I have learned that it is a good idea to batch render many sprites in OpenGL ES instead of calling glDrawArrays(..) on every sprite in the game in order to be able to render more sprites w/out a drastic reduction in frame rate.

My question is: how do I batch render 500+ particles that all have different alphas, rotations, and scales, but share the same texture atlas? The emphasis of this question is on the different alphas, rotations, and scales for each particle.

I realize this question is very similar to How do I draw 1000+ particles (w/ unique rotation, scale, and alpha) in iPhone OpenGL ES particle system without slowing down the game?, however, that question does not address batch rendering. Before I take advantage of vertex buffer objects, I want to understand batch rendering in OpenGL ES w/ unique alphas, rotations, and scales (but with the same texture). Therefore, while I plan on using VBOs eventually, I want to take this approach first.

Code examples would greatly be appreciated, and if you use an indices array as some examples do, please explain the structure and purpose of the indices array.

EDIT I am using OpenGL ES 1.1.

EDIT Below is a code example of how I render each particle in the scene. Assume that they share the same texture and that texture is already bound in OpenGL ES 1.1 before this code executes.

- (void) render {

    glPushMatrix();

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

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

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

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

    // glBindTexture(GL_TEXTURE_2D, texture[0]);

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


    glTexCoordPointer(2, GL_FLOAT, 0, texturedQuad.textureCoords);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

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

A code alternative to this method would be greatly appreciated!

1

There are 1 answers

5
Justin Meiners On BEST ANSWER

One possibility would be to include those values in a vertex attrib array - I think this is the best option. If you're using OpenGL ES 1.1 instead of 2.0 you're screwed out of this method. Vertex attrib arrays allow you to store values at each vertex in this case you could store the alphas and rotations each in their own attrib array and pass them to the shader with glVertexAttribArray. The shader would then do the rotation transformation and color processing with the alpha.

The other option would be to do rotation transformation on the CPU, and then batch particles with a similar alpha values into several draw calls. This version would require a little bit more work and it would not be a single draw call but it would still help to optimize if the shader is not an option.

NOTE: The question you linked to also recommends the array solution

EDIT: Given your code here is an OpenGL ES 1.0, here's a solution using glColorPointer:

// allocate buffers to store an array of all particle data
verticesBuffer = ... 
texCoordBuffer = ...
colorBuffer = ...

for (particle in allParticles)
{
  // Create matrix from rotation
  rotMatrix = matrix(particle.rotation.x, particle.rotation.y, particle.rotation.z)
  // Transform particle by matrix
  verticesBuffer[i] = particle.vertices * rotMatrix

  // copy other data
  texCoordBuffer[i] = particle.texCoords;
  colorBuffer[i] = color(1.0, 1.0, 1.0, particle.alpha);
}

glVertexPointer(verticesBuffer, ...)
glTexCoordPointer(texCoodBuffer, ...)
glColorPointer(colorBuffer, ...)

glDrawArrays(particleCount * 4, ...);

A good optimization for this solution would be to share the buffers for each render so you don't have to reallocate them every frame.