Friday, August 21, 2009

Creating sparks/bolts in Opengl ES

I wanted to create some sparks/lightening/bolts kinda thing on the iPhone/iPod touch using OpenGL ES. I made a quick google and found this Delphi Opengl Project. I adapted it and created a quick spark object. Here is the draw method involved:


-(void) draw
{
#define random ((float)random()/RAND_MAX)
// initialise the start and end points
yDisp[0] = yDisp[STEPS-1] = 0;

// calculate new Y coordinate. new = old + random.
for (int i = 1; i <> yDisp[i-1] + 0.075f) yDisp[i] = yDisp[i-1]+0.075f;
if (yDisp[i] <> yDisp[i+1] + 0.075f) yDisp[i] = yDisp[i+1]+0.075f;
if (yDisp[i] <> 0.5f) yDisp[i] = 0.5f;
if (yDisp[i] < -0.5f) yDisp[i] = -0.5f;
}

// Prepare the vertices as a Triangle strip
float rnd;
for (int j = 0; j < STEPS; j++)
{
rnd = 0.04f*(random-0.5f); //0.4 * random between -0.5 and 0.5
vertices[j*6 + 0] = length*j/STEPS + rnd; //x between 0 and length with some slight randomness
vertices[j*6 + 1] = -halfThickness + (yDisp[j] + rnd) * amplitude; //y
vertices[j*6 + 2] = 0; //rnd; //z

vertices[j*6 + 3] = length*j/STEPS + rnd; //x
vertices[j*6 + 4] = halfThickness + (yDisp[j] + rnd) * amplitude; //y
vertices[j*6 + 5] = 0; //rnd; //z
}

// Draw the vertices
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glColor4f(0.4f, 0.3f, 0.8f, 1.0f);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(x,y,0);
glRotatef(angleInDegrees, 0, 0, 1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, STEPS*2);
glPopMatrix();
}


The vertices array and yDisp array would need to be malloced appopriately in the init method (and freed in the dealloc):

vertices = malloc(sizeof(GLfloat) * 3 * STEPS * 2);//3 coordinates for each vertex, 2 vertices for each step
yDisp = malloc(sizeof(float) * STEPS);

Some good values for a decent spark would be length 200, halfWidth 1, amplitude 50.
STEPS was #defined to 40 for now.

This is just the beginning. I need to add more effects like glowing endpoints and subtle particle systems.

Wednesday, August 05, 2009

gluUnProject for iPhone / OpenGL ES

I wasn't surprised that gluUnProject did not exist in the iPhone SDK since they did not provide a glu library implementation. However as I have migrated the gluLookAt, it was time to migrate the gluUnProject. It has been ages since I had used this method. The only thing I remebered was that it was used to know where you have clicked in your 3d world, by using a 2d device, i.e. a mouse. In our case since we're using iPhone, it's the capacitive touch screen which gives us back 2d coordinates of where we touched on the screen.

Since I'm using a perspective view, I needed to translate those coordinates into world coordinates. In my case, on the current project I'm working on, I would finally want to know which button the user click from a grid.

I got the gluUnProject code from MESA. However the code needed some minor adjustments, namely converting everything from double to float:
  • any GLdouble had to be replaced with GLfloat
  • any double numbers, e.g. 0.0 or 1.0 I converted them to their respective float counterpart, e.g. 0.0f or 1.0f
  • and math functions which accepted/returned double, I replaced them with their float versions, e.g. fabs -> fabsf
The next problem was that gluUnProject takes the 3 coordinates, 2 of them are retrieved from the touch event, but the Z coordinate we don't know it. On the desktop openGL usually the depth buffer is queried to retrieve the depth value (between 0 and 1) at the x-y coordinate. But we cannot do this on the iPhone as it uses tile rendering.

So I thought to use the value of zero, but in effect it was always giving the coordinates of the center of the screen. In reality it was giving me the coordinates of the camera position. When I tried 1 instead of 0, it was giving me coordinates that made more sense but still not 100% precise.

The solution was to unproject twice, one time at the near plane (z = 0) and one time at the far plane (z = 1) as discussed in a forum. That gives you a ray which can then be used to make an intersection with a plane and get the exact coordinates. Thus converting from 2d to 3d.

Here's the method I have used which uses the migrated gluUnProject using just floats:

-(CGPoint) getOGLPos:(CGPoint)winPos
{
// I am doing this once at the beginning when I set the perspective view
// glGetFloatv( GL_MODELVIEW_MATRIX, __modelview );
// glGetFloatv( GL_PROJECTION_MATRIX, __projection );
// glGetIntegerv( GL_VIEWPORT, __viewport );

//opengl 0,0 is at the bottom not at the top
winPos.y = (float)__viewport[3] - winPos.y;
// float winZ;
//we cannot do the following in openGL ES due to tile rendering
// glReadPixels( (int)winPos.x, (int)winPos.y, 1, 1, GL_DEPTH_COMPONENT24_OES, GL_FLOAT, &winZ );

float cX, cY, cZ, fX, fY, fZ;
//gives us camera position (near plan)
gluUnProject( winPos.x, winPos.y, 0, __modelview, __projection, __viewport, &cX, &cY, &cZ);
//far plane
gluUnProject( winPos.x, winPos.y, 1, __modelview, __projection, __viewport, &fX, &fY, &fZ);

//We could use some vector3d class, but this will do fine for now
//ray
fX -= cX;
fY -= cY;
fZ -= cZ;
float rayLength = sqrtf(cX*cX + cY*cY + cZ*cZ);
//normalize
fX /= rayLength;
fY /= rayLength;
fZ /= rayLength;

//T = [planeNormal.(pointOnPlane - rayOrigin)]/planeNormal.rayDirection;
//pointInPlane = rayOrigin + (rayDirection * T);

float dot1, dot2;

float pointInPlaneX = 0;
float pointInPlaneY = 0;
float pointInPlaneZ = 0;
float planeNormalX = 0;
float planeNormalY = 0;
float planeNormalZ = -1;

pointInPlaneX -= cX;
pointInPlaneY -= cY;
pointInPlaneZ -= cZ;

dot1 = (planeNormalX * pointInPlaneX) + (planeNormalY * pointInPlaneY) + (planeNormalZ * pointInPlaneZ);
dot2 = (planeNormalX * fX) + (planeNormalY * fY) + (planeNormalZ * fZ);

float t = dot1/dot2;

fX *= t;
fY *= t;
//we don't need the z coordinate in my case

return CGPointMake(fX + cX, fY + cY);
}

Tuesday, August 04, 2009

Disabling Texture Units

After I managed to create multitextured polygons, I ran into another problem. After flushing the multitextured vertex array, I needed to flush a single textured vertex array for the HUD (Heads-up display, i.e. timer, score etc). The problem looked like it was using the texture coordinates of the previous vertex array.

After some googling and trying to understand what was happening, I realized that I needed to disable the 2nd texture unit. When you are using texture array pointers we use glClientActiveTexture, so before drawing the non-multitextured vertex array, I needed to disable the second texture unit and then switch back to the first texture unit

//disable 2nd texture unit
glClientActiveTexture(GL_TEXTURE1);
glDisable (GL_TEXTURE_2D);
//back to the 1st texture unit
glClientActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, [_texture name]);
...


Initially I tried using glActiveTexture instead of glClientActiveTexture. The glActiveTexture is used when display lists/immediate mode is used (glBegin...glEnd). And obviously nothing was happening.

Saturday, August 01, 2009

Multitexturing on Opengl ES

I wanted to create a random highlight animation effect on the buttons that glides over them every now and then. At first I was going to do it in a multipass approach, i.e. first draw the button polygons, then the highlight over them. However it should be more efficient (with loads of polygons anyway) if multitexturing is used. The iPhone has 2 Texture Units (TU) and so I can take advantage of that.

I never had done multitexturing in opengl before, so I had to learn the concept. If you understand the blending functions with the frame buffer, then multitexturing will be easy. The difference is that you can combine the results of texture units. Since we have two TUs we can make the first texture blend with the frame buffer, and then overlay the second texture by adding it to the result of the previous TU (GL_PREVIOUS).
You dictate how the TUs will combine by specifying whether GL_COMBINE_RGB is GL_MODULE, GL_ADD, GL_DECAL, and GL_REPLACE. One should take a look at what each one will calculate to, and also experiment a bit with them.

Here is more detailed information about texture combiners using a fixed pipeline, and what it would like if we used shaders. I must admit that shaders are easier to read, at least such simple shaders, but shaders are only supported in iPhone 3GS.

Before setting up the multitexturing, I set up the texture coordinates for both TUs:
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2, GL_FLOAT, sizeof(VertexDataMultiTextured), &vd[0].uv0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE1);
glTexCoordPointer(2, GL_FLOAT, sizeof(VertexDataMultiTextured), &vd[0].uv1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

Then I set up how the TUs should behave. In my case I had a texture for the button (in an atlas) and used the following openGL commands
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, [_texture name]);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
//blend the texture with the framebuffer(GL_PREVIOUS)
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_BLEND);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
//use the texture's alpha channel
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
//------------------------

And then I selected the second texture unit and added the color information of the glow texture (which was in the same atlas) to the result of the previous TU:
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, [_texture name]);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
//add the previous color information with the texture's color information
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
//don't effect the alpha channel, use the result (GL_PREVIOUS) of the previous texture unit
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);