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.
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.
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;
}
}
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.