Thursday, April 2, 2009

OpenGL ES for iPhone : A Simple Tutorial Part 1

It is difficult to find a beginner's book/tutorial suitable for OpenGL ES 1.1 in iPhone Development, partly because the current version of OpenGL ES is already 2.0 (there is a book on it). OpenGL ES 2.0 code is, unfortunately, NOT backward compatible with the OpenGL ES version 1.1 that iPhone use. If you rely on the materials from OpenGL (not ES) 1.5, some functions / features are not available in OpenGL ES as well. Upto now, there is no primer books on OpenGL ES 1.1 for iPhone Development Environment.

OpenGL ES specification can be obtained here
http://www.khronos.org/opengles/spec/
http://www.khronos.org/registry/gles/specs/1.1/es_full_spec.1.1.12.pdf

The OpenGL ES is an API suitable for contrained devices, and is derived from the OpenGL 1.5 specification. To achieve this goal, redundancy was removed from the OpenGL API. For example, in OpenGL ES, only vertex arrays exist and immediate mode and display lists were removed.

It is not easy to learn OpenGL ES for iPhone based on the existing available resources, so I decided to write this article based on the materials that I collected from different sources.

1) What is OpenGL ES ?
OpenGL ES is an API for advanced 3D graphics targeted at handheld devices such as iPhone. In OpenGL ES, geometric objects are drawn by specifying a series of coordinate sets that include vertice attributes like position, normal color and texture coordinates. Vertice position is specified using vertex arrays.

OpenGL ES is a "State Machine". This describes the use of state variables in OpenGL ES and commands are used for querying, enabling, and disabling states. So enabling states in different orders may have different behavior.

If you have read some OpenGL (not ES) books, you would know that OpenGL ES does not use glBegin() and glEnd(). Moreover, some OpenGL Extensions are not available in iPhone as well. For the list of supported Extensions in iPhone and iPhone Simulator, please refer to "Drawing With OpenGL ES" in iPhone Application Programming Guide from Apple.

2) How to start learning OpenGL ES ?
The most easiest way is to learn Open GL ES by actual coding and running in an emulator and iPhone Simulator from the SDK is a perfect emulator to test the OpenGL ES code. In iPhone SDK, you can create a new project from the OpenGL ES Application Template and without any modification, after you build & go in Simulator, you will see a colored square rotating at the middle of the screen. If you look closely into the source code, you will notice that the rotating square is defined and created inside the (void)drawView method of the source file EAGLView.m

From the source code, we could learn how to do basic drawings in OpenGL ES for iPhone. Here is the step by step tutorial and we have taken the approach to explain the existing code inside drawView() method first, so that you know what the code does and where to change the code if you want to mess around. You may also consider to use the Snapshot feature of XCode which is a very good "time machine" for your XCode Project.

3) How to specify the coordinates for a square ?
  const GLfloat squareVertices[] = {
    -0.5f, -0.5f,    // position v0
     0.5f, -0.5f,    // position v1
    -0.5f,  0.5f,    // position v2
     0.5f,  0.5f,    // position v3
  };

Each set of 2 floats given above specifies the x, y value of a vertex. The x is the horizontal position, the y is the vertical position that should be. If you give the third float number in the set, this would be z coordinate. The more positive the z coordinate, the more out of the screen (towards you) it is. Later tutorials, we will show you how to draw a pyramid which is a 3D model.

For the vertices position of each set in the square, please refer to diagram of point (4) below.

The vertices positions correspond to the different OpenGL ES Primitive Types in glDrawArrays() function that you will use later.

4) How to specify the colors for a square ?
  const GLubyte squareColors[] = {
    255, 255,   0, 255,    // yellow color
      0, 255, 255, 255,    // cyan color
      0,   0,   0,   0,    // black color
    255,   0, 255, 255,    // magenta color
  };


The colors are specified in groups of 4 given as above. The first color is red+green=yellow, the second is green+blue=cyan and the third is black and the fourth is red+blue=magenta. The first three parameters indicate your red, green and blue values. The larger the value, the brighter the color. If all values are 0, you get black. If all values are 255, you get white. The last value is the alpha value and is used for transparency. 0 is completely transparent.



5) OpenGL projection matrix glMatrixMode(GL_PROJECTION);
The next OpenGL function is glMatrixMode. When working with OpenGL, there are various matrices that are available to work with. These define the view and how primitives should be placed. When transforming primitives later, the GL_MODELVIEW matrix will be used. If the projection (how objects are viewed) need to be changed later, GL_PROJECTION matrix will be used.

6) Identity Matrix glLoadIdentity();
This is a 4 x 4 matrix with 1's on the diagonals and 0's everywhere else. The identity matrix has the same properties as the value 1 in normal multiplication. Any matrix that multiply by the identity matrix will stay the same. The loading of identity matrix function glLoadIdentity() takes no parameters. We use the glLoadIdentity(); to clear the currently modifiable matrix for future transformation commands, as these commands modify the current matrix. Typically, we call this command before specifying projection or viewing transformations and also might call it before specifying a modeling transformation.

7) Orthographic Projection glOrthof(left, right, bottom, top, near, far);
There are 2 ways to view things. One is by viewing objects in an Orthogrpahic manner and the other is viewing objects in a perspective view. If you have ever looked down a long road, you will notice that the road gets smaller and smaller the further down you look. This is a perspective view. In an Orthographic view, the road would remain the same width as far as you could see. Orthographic projection runs quicker than perspective projection. This does not mean that we should always use it though. Sometimes you do not necessarily want depth eg. for 2D applications or games. Here, orthographic projection is extremely useful.

The glOrthof() function is used to specify orthographic projection. This function takes 6 parameters, each explained below.


GLfloat left & GLfloat right
        These specify the values for the left and right clipping planes.

GLfloat bottom & GLfloat top
        These specify the values for the bottom and top clipping planes.

GLfloat near & GLfloat far
        These specify the drawing distance. Any objects out of this range will
        not be displayed.




All parameters above specify an area that will be displayed in the window. Looking back at the vertices specified for the square, you can see that the square will be displayed in the center of the window.


8) Rotating the square glRotatef(3.0f, 0.0f, 0.0f, 1.0f);
The rotation transformation is done using the glRotatef() function. The first parameter specifies the angle in degrees to rotate the objects, positive float value means counter-clockwise direction, negative float means clockwise. The next 3 parameters are used to specify in what axis to rotate. These specify a vector. A value of 1.0 is normally used to specify an axis. If you comment out the glRotatef() function call, you will see the square in steady position and we have numbered the vertices position in the diagram below.



9) Clearing color for the background glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
The first three parameters indicate your red, green and blue values for the background color. The larger the value, the brighter the color. If all values are 0, you get black. If all values are 1, you get white. The last value is the alpha value. This is used for transparency. 1.0f is completely opaque and 0.0f is completely transparent.

10) Clearing the color buffer glClear(GL_COLOR_BUFFER_BIT);
This function accepts one parameter being which buffers you want to clear. This causes the screen to be cleared to the grey color specified in point (9) above.

11) Set the current Vertex Array glVertexPointer(2, GL_FLOAT, 0, squareVertices);
Primitives in OpenGL ES are drawn using Vertex Arrays. You need to set the current vertices to use. This is accomplished by using the glVertexPointer function. This function takes 4 parameters :

GLint size
        This specifies the number of coordinates per vertex.
        As we had left off the 0.0f's in the square vertices above,
        we would place a 2 here.
        If each vertex has been specified using 3 values (x,y,z) ,
        we would use a 3 for this parameter.

GLenum type
        specifies what data type is being used for the values in the array
        eg. GL_BYTE, GL_SHORT, GL_FLOAT, etc.

GLsizei stride
        specifies the offset between consecutive vertices ie. how many
        extra values exist between the end of the current vertex and
        the beginning of the next vertex. We do not have any extra data
        between vertices and so we therefore pass 0 for this parameter.

const GLvoid *pointer
        The last parameter is used to specify the memory location of
        the first value in the array of values ie. the pointer to the array.


12) Enable the Vertex Array glEnableClientState(GL_VERTEX_ARRAY);
Like the Vertex Array, there are a number of other arrays that will be covered. If not all of these arrays are being used, resources can be saved by disabling the others. All are disabled by default and so we therefore need to enable the Vertex Array. This is done by using the glEnableClientState(GL_VERTEX_ARRAY) function. This function takes one parameter, specifying the array that must be enabled. We therefore pass the GL_VERTEX_ARRAY flag to enable the Vertex Array.

13) Set the current Color Array glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
Similar to Vertex Array, color array is setup by glColorPointer() function. We pass a 4 as the first parameter to indicate that there are 4 floats per vertex. Other parameters are similar to that of glVertexPointer().

If you want to change the shading mode, you can use glShadeModel() function. The default is smooth shaded glShadeModel(GL_SMOOTH) and the other option is flat shaded glShadeModel(GL_FLAT).

14) Enable the Color Array glEnableClientState(GL_COLOR_ARRAY);
The Color Array is enabled by using the glEnableClientState(GL_COLOR_ARRAY) function

15) Display a shape on the screen glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
Finally, we need to display the shape on the iPhone screen using the glDrawArrays() function. The parameters are as follows :

GLenum mode
        This is used to specify what primitive to draw. see point (16) below.
GLint first
        This is used to specify the starting index in the array
        ie. how many vertices in the array to skip before starting to read them.
        We want to start from the beginning, thus requiring a value of 0 to be passed.

GLsizei count
        This specifies how many vertices to process. We have 4 vertices
        that we want to use. This is the value that we need to pass to the function.


16) OpenGL Primitives
The first parameter is primitive flag and OpenGL has defined the following types. Primitives are created by specifying vertices. These are points in 3D space which specify points on the shape. A list of primitives are given below along with how their vertices should be placed (also refer to the diagram below). The primitive type defined the order of the vertices are drawn.

Primitive Flag       Description
GL_POINTS            A point is placed at each vertex.
GL_LINES             A line is drawn for every pair of vertices that are given.
GL_LINE_STRIP        A continuous set of lines are drawn. After the first
                     vertex, a line is drawn between every successive vertex
                     and the vertex before it.
GL_LINE_LOOP         This is the same as GL_LINE_STRIP except that the start and end
                     vertices are connected as well.
GL_TRIANGLES         For every triplet of vertices, a triangle is drawn with corners
                     specified by the coordinates of the vertices.
                     It begins with v0,v1,v2 then v3,v4,v5 and so on.
GL_TRIANGLE_STRIP    After the first 2 vertices, every successive vertex uses the
                     previous 2 vertices to draw a triangle.
                     It begins with v0,v1,v2 then v1,v2,v3 then v2,v3,v4 and so on.
GL_TRIANGLE_FAN      After the first 2 vertices, every successive vertex uses the
                     previous vertex and the first vertex to draw a triangle.
                     This is used to draw cone-like shapes
                     It begins with v0,v1,v2 then v0,v2,v3 then v0,v3,v4 and so on.
GL_QUADS             Draws a series of quadrilaterals (four sided polygons)
                     beginning with v0,v1,v2,v3 then v4,v5,v6,v7 and so on
GL_QUAD_STRIP        Does a series of quadrilaterals beginning with
                     v0,v1,v3,v2 then v2,v3,v5,v4 then v4,v5,v7,v6 and so on
GL_POLYGON           Draws a polygon using the points v0,v1,...,vn
                     as vertices. n must be at least 3, or nothing is drawn.




17) How to change it to a triangle, move/scale the object ?
You can simply change the last parameter of glDrawArrays to 3 like below, you will see a rotating triangle. By using the parameter 3, the code ignores the 4th position of the Array. If you want to change the triangle position or the shape, you should change the values in the array or use transform function to move the triangle.

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);


You can use the glTranslatef() function to move the triangle and the glTranslatef() function should be inserted before the glMatrixMode function as below.
    glTranslatef(0.25f, 0.5f, 0.0f);
    glMatrixMode(GL_MODELVIEW);
    glRotatef(3.0f, 0.0f, 0.0f, 1.0f);


We pass 3 floats to the glTranslatef() function, specifying the value to move the drawn objects in the x, y and z direction. Our first transformation is to move the triangle right 0.25 units and up 0.5 units.

You can use the glScalef() function to scale the triangle and the glScalef() function should be inserted before the glMatrixMode function as below. The code below reduces the size of the triangle by half. You can scale an object a different amount in each axis. Try to experiment different values for the parameters of glScalef() function to see the effect.
    glScalef(0.5f, 0.5f, 0.5f);
    glMatrixMode(GL_MODELVIEW);
    glRotatef(3.0f, 0.0f, 0.0f, 1.0f);


18) Transformation: "translate first then rotate" or "rotate first then translate" ?

The output of the following codes by changing the order of glTranslatef() and glRotatef() functions will be different. You can try these codes and see the results in the Simulator.

Please take note that these glTranslatef() and glRotatef() function calls are before glMatrixMode(GL_MODELVIEW);


    glTranslatef(0.0f, 0.5f, 0.0f);        // translate first
    glRotatef(30.0f, 0.0f, 0.0f, 1.0f);    // then rotate
    glMatrixMode(GL_MODELVIEW);

is different from

    glRotatef(30.0f, 0.0f, 0.0f, 1.0f);    // rotate first
    glTranslatef(0.0f, 0.5f, 0.0f);        // then translate
    glMatrixMode(GL_MODELVIEW);


If you try these codes above, and translate something to the right and then rotate it, you will view a rotated shape sitting on the horizontal axis. If you first rotate the object and then translate it, you will view a rotated shape that has been moved out diagonally from the origin.

19) Transformation after glMatrixMode(GL_MODELVIEW);

Try changing the order or values of the glTranslatef() and glRotatef() functions below to see what affect each one has.

    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0f, 0.05f, 0.0f);      // move the object
    glRotatef(3.0f, 0.0f, 0.0f, 1.0f);    // rotate the object


20) How to change it to a polygon ?
If you change these codes, you will see a rotating polygon outline as below, we have also numbered the vertices position in the diagram.
  const GLfloat squareVertices[] = {
    -0.5f, -0.5f,    // position v0
    -0.5f,  0.5f,    // position v1
     0.0f, 0.75f,    // position v2
     0.5f,  0.5f,    // position v3
     0.5f, -0.5f,    // position v4
  };

  const GLubyte squareColors[] = {
    255, 255,   0, 255,    // yellow color
      0, 255, 255, 255,    // cyan color
      0,   0,   0,   0,    // black color
    255,   0,   0,   1,    // red color
    255,   0, 255, 255,    // magenta color
  };


  glDrawArrays(GL_LINE_LOOP, 0, 5);



You may notice that the primitive types for GL_QUADS, GL_QUAD_STRIP & GL_POLYGON are not available in OpenGL ES. To draw the polygon as below, you need to use GL_TRIANGLE_STRIP with these codes

  const GLfloat squareVertices[] = {
    -0.5f, -0.5f,    // position v0
     0.5f, -0.5f,    // position v1
    -0.5f,  0.5f,    // position v2
     0.5f,  0.5f,    // position v3
     0.0f,  0.75f,   // position v4
  };

and

  glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);


It does a series of triangle drawings beginning with v0, v1, v2 then v1, v2, v3 and finally v2, v3, v4.




You can download the old OpenGLTemplate from here http://www.2shared.com/file/vebAnLSs/OpenGLTemplate.html
.
.

17 comments:

Anonymous said...

Thanks for writing this. It was very helpful

Dan G said...

Agreed - this got me up and messing around with OpenGL faster than other tutorials I'd tried!

Particularly helpful was the explanation of the different GL draw array modes...

Anonymous said...

Are you aware of a C++ library that can be used with OpenGL ES for the iPhone? There are some very obvious classes that could be defined for objects in the interface.

chilo said...

really nice tutorial, thxs for sharing!!

Surendra said...

It is amazing tut.I learn many things by this example and cleared many functions

Thanx a lot to write,
if you have time than write more there thanx.

Mohit said...

Hi Friend

Thanks a lot for this great post...This solved my confusion regarding basics of opengl es...

Please continue with exploring opengl ES...

Mohit said...

Hi Friend..
Thanks for this great post....
this helped me in understanding the basic of opengl es...

Please go ahead with exploring the iPhone....

tommy32 said...

That's very good! Thank you!

gonzobrains said...

I don't know why, but when I try to run the default OpenGL project I don't see the cube drawing code execute. Is there something I'm missing here?

Offshore software development company said...

Nice article, I was looking for these kind of information on software development. Please continue writing....

Software Development India said...

OMG!!!!! I have never found such tut in such simple and easily understood way. Thanks a lot mate for sharing the stuff with us.

Jay said...

Thanks for the excellent explanation! Keep up the good work!

Anonymous said...

Thanks it was really useful to learn the principles of opengl drawing.

Though the latest sdk template does not use the code part what is explained under transformation. It uses different API calls:

// Update attribute values.
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
glEnableVertexAttribArray(ATTRIB_COLOR);

Can you maybe explain these calls?

Best regards,
Sandor

Anonymous said...

great

Paras Joshi said...

Realy nice tutorial thanx... :)

Garrett Rowland said...

I have never discovered such tut in such easy and quickly recognized way. Thanks for discussing the things with us.

Anonymous said...

Sweat !