# OpenGL ES 1.1 - How to batch draw particles with different translations?

Assuming that I've combined all of my vertex data for many particles into a single array, how would I batch draw all of those particles in a manner that preserves their unique translations?

Any code examples would be greatly appreciated.

-
Are there any ios devices out there where it makes sense to use opengl es 1.1 anymore? – Jari Komppa Dec 29 '12 at 6:55
Still runs noticeably faster than naive OpenGL ES 2.0 on iPhone 3GS and below, last time I tested (which was about a year ago). – Trevor Powell Dec 29 '12 at 7:47
As far as I recall, 3GS isn't even supported by current ios devel tools anymore.. =) – Jari Komppa Dec 29 '12 at 10:53
Does my usage of OpenGL ES 1.1 hinder my ability to batch draw particles with different translations? If so, could you explain, using some code examples, how I might accomplish this more reasonably in OpenGL ES 2.0? – TrueLifeCoder Dec 29 '12 at 16:47
@TrueLifeCoder Could you add some code, what do you mean by "combined" and "preserves". From my understanding you can have an array of vertices, and then loop through all particles updating their vertex positions and render all of them at once with some CPU performance penalties. Or have an array of transformations, then loop through all transformations and render just the current transformation, incurring in some GPU performance drop. – rraallvv Jan 3 '13 at 16:23

Are your vertices something like topleft position:-1,-1, bottomright position +1,+1? All being rendered to the center of the screen? And you're moving them around with the worldview matrix?

To use vertex arrays and give each particle individual positions you need to do the transformation on the CPU. For each particle you have a set of vertices 4 if you're doing a quad, 6 if it's made from two triangles. Lets say the particle is 2x2 and the center is 0,0.

topleft [-1, 1]
topright [1, 1]
bottomleft [-1,-1]
bottomright [1, -1]

If you drew this it would create a particle centered around the origin of your scene, how big it appears would depend on your projection matrix. If you've got a position for each particle all you need do is going through all the vertices and add them to the particle position.

position = Vector(100, 50, -20)

topleft [-1, 1]  = position + topleft
topright [1, 1] = position + topright
bottomleft [-1,-1] = position + bottomleft
bottomright [1, -1] = position + bottomright

Do that for all your particles each time they change position. This is reasonably common solution, you might think it seems quite slow (but it's not too bad) try it out and see how it works in your case.

If you are pushing a lot of particles and find you need things to be faster then it's probably best looking at doing the entire particle effect as a shader, that way you reduce the communication between the gpu and cpu.

-

Just put your particles coordinates to buffer (geometryBuf in example) considering unique translations for each. And batch draw it with glDrawArrays().

glEnableClientState(GL10.GL_VERTEX_ARRAY);

glVertexPointer(2, GL10.GL_FIXED, 0, geometryBuf);

glDrawArrays(GL10.GL_TRIANGLES, 0, count * 6);

glDisableClientState(GL10.GL_VERTEX_ARRAY);

Params depend on particles type.

-

In my OpenGL ES 1.1 particle system I solved this by drawing one alpha blended textured triangle per particle. Another option would be to draw quads (or two-tri strips), but i found single tris to be OK for my application which has many small particles. If you have few large particles, quads might be better (fill-rate vs trig).

For each particle in my list I calculated the corners of the triangle by simple vector math and a pre-calculated look-up table. I would also make sure that each triangle faced the camera using a billboarding helper class. In other words I did not use any OpenGL transformations on the individual particles. This code would also handle rotating the sprite and/or scaling of the individual sprites.

Each corner of the triangle would be appended to the vertex buffers so that 3*3 floats would be added per triangle.

Here is the snippet from my code that does the actual building of the vertices buffer (method on the particle object, called once per particle per update):

void calculateVerticesFromPoints(float[] eye, GraphicsEngine ge, float verts[], float colors[], int offset) {

BillboardHelper.doBillboard(ParticleSystem.BILLBOARD_MATRIX, mPosition, ge.getActiveCamera().getPosition(), true);

for (int i = 0; i < 3; i++) {
final int j = i * ParticleSystem.VECTOR_STRIDE;
final int k = offset + j;
mParticleSystem.mTemp[0] = mPosition[0] + ParticleSystem.TRI_CORNERS[j + 0] * mParticleSystem.mSize;
mParticleSystem.mTemp[1] = mPosition[1] + ParticleSystem.TRI_CORNERS[j + 1] * mParticleSystem.mSize;
mParticleSystem.mTemp[2] = mPosition[2] + ParticleSystem.TRI_CORNERS[j + 2] * mParticleSystem.mSize;
mParticleSystem.mTemp[3] = 1.0f;
Matrix.multiplyMV(verts, k, ParticleSystem.BILLBOARD_MATRIX, 0, mParticleSystem.mTemp, 0);
colors[k + 0] = 1.0f;
colors[k + 1] = 0.5f;
colors[k + 2] = 0.0f;
colors[k + 3] = 1.0f;
//Log.d(TAG, String.format(" %d (%.2f,%.2f,%.2f)", k, verts[k + 0], verts[k + 1], verts[k + 2]));
}

}

I have also included my entire billboarding helper for good measure :>

package com.playmio.android.graphicsengine.scene.primitives;

import android.opengl.Matrix;

public class BillboardHelper {

// This is the original lookAt vector for billboards in world coordinates
private static final float  billboardLookAt[]   = { 0, 0, 1 };

private static final float  objToCamProj[]      = new float[3];
private static final float  objToCam[]          = new float[3];
private static final float  upAux[]             = new float[3];

public static void doBillboard(float matrix[], float pos[], float cam[], boolean spherical) {
Matrix.setIdentityM(matrix, 0);

// ObjToCamProj is the vector in world coordinates from the local origin to the camera projected in the XZ plane
objToCamProj[0] = cam[0] - pos[0];
objToCamProj[1] = 0.0f;
objToCamProj[2] = cam[2] - pos[2];

// Normalize both vectors to get the cosine directly afterwards
mathsNormalize(objToCamProj);

// Easy fix to determine whether the angle is negative or positive for positive angles upAux will be a vector pointing in the positive y direction, otherwise upAux will point downwards effectively reversing the rotation.
mathsCrossProduct(upAux, billboardLookAt, objToCamProj);

// Compute the angle
float angleCosine = mathsInnerProduct(billboardLookAt, objToCamProj);

// Perform the first rotation. The if statement is used for stability reasons if the lookAt and objToCamProj vectors are too close together then |angleCosine| could be bigger than 1 due to lack of precision
if ((angleCosine < 0.99990) && (angleCosine > -0.9999)) {
Matrix.rotateM(matrix, 0, (float) (Math.acos(angleCosine) * 180 / 3.14), upAux[0], upAux[1], upAux[2]);
}

if (spherical) {
// So far it is just like the cylindrical billboard. The code for the second rotation tilts the object so that it faces the camera objToCam is the vector in world coordinates from the local origin to the camera
objToCam[0] = cam[0] - pos[0];
objToCam[1] = cam[1] - pos[1];
objToCam[2] = cam[2] - pos[2];

// Normalize to get the cosine afterwards
mathsNormalize(objToCam);

// Compute the angle between objToCamProj and objToCam, i.e. compute the required angle for the lookup vector
angleCosine = mathsInnerProduct(objToCamProj, objToCam);

// Tilt the object. The test is done to prevent instability when objToCam and objToCamProj have a very small angle between them
if ((angleCosine < 0.99990) && (angleCosine > -0.9999)) {
Matrix.rotateM(matrix, 0, (float) (Math.acos(angleCosine) * 180 / Math.PI), (objToCam[1] < 0) ? 1 : -1, 0, 0);
}
}
}

private static float mathsInnerProduct(float[] v, float[] q) {
return ((v)[0] * (q)[0] + (v)[1] * (q)[1] + (v)[2] * (q)[2]);
}

private static void mathsCrossProduct(float[] a, float[] b, float[] c) {
(a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2];
(a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0];
(a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1];

}

private static void mathsNormalize(float[] v) {
float d = (float) (Math.sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])));
v[0] = v[0] / d;
v[1] = v[1] / d;
v[2] = v[2] / d;
}

}
-
OOPS! I didn't see the iOS tag! sorry! I leave my answer, as it might be portable enough(?) for you to use, and who knows, someone with the same problem on Android might stumble upon this page! – Lennart Rolland Feb 19 '13 at 22:22