Monday, April 20, 2009

OpenGL ES From the Ground Up, Part 2: A Look at Simple Drawing

Okay, there's still a lot of theory to talk about, but before we spend too much time getting bogged down in complex math or difficult concepts, let's get our feet wet with some very basic drawing in OpenGL ES.

If you haven't already done so, grab a copy of my Empty OpenGL Xcode project template. We'll use this template as a starting point rather than Apple's provided one. You can install it by copying the unzipped folder to this location:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/

This template is designed for a full-screen OpenGL application, and has an OpenGL view and a corresponding view controller. The view is designed to be pretty hands-off and you shouldn't need to touch it most of the time. It handles some of the gnarley stuff we'll be talking about later on, like buffer swapping, but it calls out to its controller class for two things.

First, it calls out to the controller once when the view is being setup. The view controller's setupView: method gets called once to let the controller add any setup work it needs to do. This is where you would set up your viewport, add lights, and do other setup relevant to your project. For today, ignore that method. There's a very basic setup already in place that will let you do simple drawing. Which brings us to the other method.

The controller's drawView: method will get called at regular intervals based on the value of a constant called kRenderingFrequency. The initial value of this is 15.0, which means that drawView: will get called fifteen times a second. If you want to change the rendering frequency, you can find this constant defined in the file called ConstantsAndMacros.h.

For our first trick, let's add the following code to the existing drawView: method in GLViewController.m:

- (void)drawView:(GLView*)view;
{
Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);

glLoadIdentity();
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, &triangle);
glDrawArrays(GL_TRIANGLES, 0, 9);
glDisableClientState(GL_VERTEX_ARRAY);
}


Before we talk about what's going on, go ahead and run it, and you should get something that looks like this:

iPhone SimulatorScreenSnapz001.jpg


It's a simple method; you could probably figure out what's going on if you try, but let's walk through it together. Since our method draws a triangle, we need three vertices, so we create three of those Vertex3D objects we talked about in the previous posting in this series:

    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);


You should notice that the z value for all three vertices is the same, and that that value (-3.0) is "behind" the origin. Because we haven't done anything to change it, we're looking into our virtual world as if we were standing on the origin, which is the default starting location. By placing the triangle at a z-position of -3, we ensure that we can see it on the screen.

After that, we create a Triangle3D object made up of those three vertices.

    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);


Now, that's pretty easy code to understand, right? But, behind the scenes, what it looks like to the computer is an array of 9 GLfloats. We could have accomplished the same thing by doing this:

    GLfloat  triangle[] = {0.0, 1.0, -3.0, 1.0, 0.0, -3.0, -1.0, 0.0, -3.0};


Well, not quite exactly the same thing - there's one very minor but important difference. In our first example, we have to pass the address of our Triangle3D object into OpenGL (e.g. &triangle), but in the second example using the array, we'd simply pass in the array, because C arrays are pointers. But, don't worry too much about that, because this example will be the last time we declare a Triangle3D object this way. I'll explain why in a moment, but let's finish going through our code. The next thing we do is load the identity matrix. I'll devote at least one whole posting to transformation matrices, what they are and how they are used, but just think of this call as a "reset button" for OpenGL. It gets rid of any rotations, movement, or other changes to the virtual world and puts us back at the origin standing upright.

    glLoadIdentity();


After that, we tell OpenGL that all drawing should be done over a light grey background. OpenGL generally expects colors to be defined using four clamped values. Remember from the previous post, clamped floats are floating point values that run from 0.0 to 1.0. So, we define colors by their red, green, and blue components, along with another component called alpha, which defines how much of what's behind the color shows through. Don't worry about alpha for now - for the time being, we'll just always set alpha to 1.0, which defines an opaque color.

    glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


To define white in OpenGL, we'd pass 1.0 for all four components. To define an opaque black, we'd pass 0.0 for the red, green, and blue components and 1.0 for alpha. The second line of code in that last example is the one that actually tells OpenGL to clear out anything that's been drawn before and erases everything to the clear color.

You're probably wondering what the two arguments to the glClear() call are. Well, again, we don't want to get too far ahead of ourselves, but those are constants that refer to values stored in a bitfield. OpenGL maintains a number of buffers, which are just chunks of memory used for different aspects of drawing. By logical or'ing these two particular values together, we tell OpenGL to clear two different buffers - the color buffer and the depth buffer. The color buffer stores the color for each pixel of the current frame. This is basically what you see on the screen. The depth buffer (sometimes also called the "z-buffer") holds information about how close or near the viewer each potential pixel is. It uses this information to determine whether a pixel needs to be drawn or not. Those are the two buffers you'll see most often in OpenGL. There are others, such as the stencil buffer and the accumulation buffer, but we're not going to talk about those, at least for a while. For now, just remember that before you draw a frame, you need to clear these two buffers so that the previous contents doesn't mess things up for you.

After that, we enable one of OpenGL's features called vertex arrays. This feature could probably just be turned on once in the setupView: method, but as a general rule, I like to enable and disable the functionality I use. You never know when another piece of code might be doing things differently. if you turn what you need on and then off again, the chances of problems are greatly reduced. In this example, say we had another class that didn't use vertex arrays to draw, but used vertex buffer objects instead. If either of the chunks of code left something enabled or didn't explicitly enable something they needed, one or both of the methods could end up with unexpected results.

    glEnableClientState(GL_VERTEX_ARRAY);


The next thing we do is set the color that we're going to draw in. This line of code sets the drawing color to a bright red.

    glColor4f(1.0, 0.0, 0.0, 1.0);


Now, all drawing done until another call to glColor4f() will be done using the color red. There are some exceptions to that, such as code that draws a textured shape, but basically, setting the color like this sets the color for all calls that follow it.

Since we're drawing with vertex arrays, we have to tell OpenGL where the array of vertices is. Remember, an array of vertices is just a C array of GLfloats, with each set of three values representing one vertex. We created a Triangle3D object, but in memory, it's exactly the same as nine consecutive GLfloats, so we can just pass in the address of the triangle.

    glVertexPointer(3, GL_FLOAT, 0, &triangle);


The first parameter to glVertexPointer() indicates how many GLfloats represent each vertex. You can pass either 2 or 3 here depending on whether you're doing two-dimensional or three-dimensional drawing. Even though our object exists in a plane, we're drawing it in a three-dimensional virtual world and have defined it using three values per vertex, so we pass in 3 here. Next, we pass in an enum that tells OpenGL that our vertices are made up of GLfloats. They don't have to be - OpenGL ES is quite happy to let you use most any datatype in a vertex array, but it's rare to see anything other than GL_FLOAT. The next parameter... well, don't worry about the next parameter. That's a topic for future discussion. For now, it will always, always, always be 0. In a future posting, I'll show you how to use this parameter to interleave different types of data about the same object into a single data structure, but that's heavier juju than I'm ready to talk about now.

After that, we tell OpenGL to draw triangles between the vertices in the array we previously submitted.

    glDrawArrays(GL_TRIANGLES, 0, 9);


As you probably guessed, the first parameter is an enum that tells OpenGL what to draw. Although OpenGL ES doesn't support quads or any other polygon besides triangles, it does still support a variety of drawing modes, including the ability do draw points, lines, line loops, triangle strips, and triangle fans. We'll talk about the various drawing modes later. For now, let's just stick with triangles.

Finally, we disable the one feature that we enabled earlier so we don't mess up other code elsewhere. Again, there is no other code in this example, but usually when you're using OpenGL, drawing is potentially happening from multiple objects.

    glDisableClientState(GL_VERTEX_ARRAY);


And that's it. It works. It's not very impressive, and it's not very efficient, but it works. You're drawing in OpenGL. Yay! A certain number of times every second, this method is getting called, and it's drawing. Don't believe me? Add the following bold code to the method and run it again:

- (void)drawView:(GLView*)view;
{
static GLfloat rotation = 0.0;

Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);

glLoadIdentity();
glRotatef(rotation, 0.0, 0.0, 1.0);
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, &triangle);
glDrawArrays(GL_TRIANGLES, 0, 9);
glDisableClientState(GL_VERTEX_ARRAY);

rotation+= 0.5;
}


When you run it again, the triangle should slowly revolve around the origin. Don't worry too much about the mechanics of rotation, I just wanted to show you that your drawing code was getting called many times a second.

What if we want to draw a square? Well, OpenGL ES doesn't have squares, so we have to define a square out of triangles. That's easy enough to do - a square can be created out of two right triangles. How do we tweak the code above to draw two triangles, rather than one? Can we create two Triangle3Ds and submit those? Well, yeah, we could. But that would be inefficient. It would be better if we submitted both triangles as part of the same vertex array. We can do that by declaring an array of Triangle3D objects, or by allocating a chunk of memory that happens to be the same size as two Triangle3Ds or eighteen GLfloats.

Here's one way:

- (void)drawView:(GLView*)view;
{
Triangle3D triangle[2];
triangle[0].v1 = Vertex3DMake(0.0, 1.0, -3.0);
triangle[0].v2 = Vertex3DMake(1.0, 0.0, -3.0);
triangle[0].v3 = Vertex3DMake(-1.0, 0.0, -3.0);
triangle[1].v1 = Vertex3DMake(-1.0, 0.0, -3.0);
triangle[1].v2 = Vertex3DMake(1.0, 0.0, -3.0);
triangle[1].v3 = Vertex3DMake(0.0, -1.0, -3.0);

glLoadIdentity();
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, &triangle);
glDrawArrays(GL_TRIANGLES, 0, 18);
glDisableClientState(GL_VERTEX_ARRAY);
}


Run it now, and you should get something like this:

iPhone SimulatorScreenSnapz002.jpg


That code is less than ideal, however, because we're allocating our geometry on the stack, and we're causing a additional memory to be used because our Vertex3DMake() method creates a new Vertex3D on the stack, and then copies the values into the array.

For a simple example like this, that works fine, but in more complex cases, the geometry for defining 3D objects will be large enough that you don't want to be allocating it on the stack and you don't want to be allocating memory more than once for a given vertex, so it's a good idea to get in the habit of allocating your vertices on the heap by using our old friend malloc() (although I sometimes like to use calloc() instead because by setting all the values to zero, some errors are easier to track down). First, we need a function to set the values of an existing vertex instead of creating a new one the way Vertex3DMake() does. This'll work:

static inline void Vertex3DSet(Vertex3D *vertex, CGFloat inX, CGFloat inY, CGFloat inZ)
{
vertex->x = inX;
vertex->y = inY;
vertex->z = inZ;
}


Now, here's the exact same code re-written to allocate the two triangles on the heap using this new function:

- (void)drawView:(GLView*)view;
{
Triangle3D *triangles = malloc(sizeof(Triangle3D) * 2);

Vertex3DSet(&triangles[0].v1, 0.0, 1.0, -3.0);
Vertex3DSet(&triangles[0].v2, 1.0, 0.0, -3.0);
Vertex3DSet(&triangles[0].v3, -1.0, 0.0, -3.0);
Vertex3DSet(&triangles[1].v1, -1.0, 0.0, -3.0);
Vertex3DSet(&triangles[1].v2, 1.0, 0.0, -3.0);
Vertex3DSet(&triangles[1].v3, 0.0, -1.0, -3.0);

glLoadIdentity();
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, triangles);
glDrawArrays(GL_TRIANGLES, 0, 18);
glDisableClientState(GL_VERTEX_ARRAY);

if (triangles != NULL)
free(triangles);
}


Okay, we've covered a lot of ground, but let's got a little further. Remember how I said that OpenGL ES has more than one drawing mode? Well, this square shape that currently requires 6 vertices (18 GLfloats) to draw can actually be drawn with just four vertices (12 GLfloats) using the drawing mode known as triangle strips (GL_TRIANGLE_STRIP).

Here's the basic idea behind a triangle strip: the first triangle in the strip is made up of the first three vertices (indexes 0, 1, 2). The second triangle is made up of two of the vertices from the previous triangle along with the next vertex in the array, and so on through the array. This picture might make more sense - the first triangle is vertices 1, 2, 3, the next is vertices 2, 3, 4, etc.:



So, our square can be made like this:

triangle_strip_square.png


The code to do it this way, looks like this:

- (void)drawView:(GLView*)view;
{
Vertex3D *vertices = malloc(sizeof(Vertex3D) * 4);

Vertex3DSet(&vertices[0], 0.0, 1.0, -3.0);
Vertex3DSet(&vertices[1], 1.0, 0.0, -3.0);
Vertex3DSet(&vertices[2], -1.0, 0.0, -3.0);
Vertex3DSet(&vertices[3], 0.0, -1.0, -3.0);

glLoadIdentity();

glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 12);
glDisableClientState(GL_VERTEX_ARRAY);

if (vertices != NULL)
free(vertices);
}


Let's go to the first code sample to see something. Remember how we drew that first triangle? We used glColor4f() to set a color and said that it would set the color for all calls that follow. That means that every object defined in a vertex array has to be drawn in the same color? What? That's pretty limiting, isn't it?

Well, no. Just as OpenGL ES will allow you to pass vertices all at once in an array, it will also let you pass in a color array to specify the color to be used for each vertex. If you choose to use a color array, you need to have one color (four GLfloats) for each vertex. Color arrays have to be turned on using

glEnableClientState(GL_COLOR_ARRAY);


Otherwise, the process is basically the same as passing in vertex arrays. We can use the same trick be defining a Color3D struct that contains four GLfloat members. Here's how you could pass in a different color for each array of that original triangle we drew:

- (void)drawView:(GLView*)view;
{
Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);

Color3D *colors = malloc(sizeof(Color3D) * 3);
Color3DSet(&colors[0], 1.0, 0.0, 0.0, 1.0);
Color3DSet(&colors[1], 0.0, 1.0, 0.0, 1.0);
Color3DSet(&colors[2], 0.0, 0.0, 1.0, 1.0);

glLoadIdentity();
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, &triangle);
glColorPointer(4, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLES, 0, 9);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);

if (colors != NULL)
free(colors);
}


If you run that, it should create a triangle that looks like this:

iPhone SimulatorScreenSnapz003.jpg


Let's look at one more thing today. One problem with the way we've been doing things is that if a vertex is used more than once (except in conjoining triangles in a triangle strip or triangle fan), you have to pass the same vertex into OpenGL multiple times. That's not a big deal, but you generally want to minimize the amount of data you're pushing into OpenGL, so pushing the same 4-byte floating point value over and over is less than ideal. In some meshes, a vertex could conceivably be used in seven or more different triangles, so your vertex array could be many times larger than it needs to be.

When dealing with these complex geometries, there's a way to avoid sending the same vertex multiple times, and it's by using something called elements to refer to vertices by their index in the vertex array. How this works is you'd create a vertex array that has each vertex once and only once. Then, you'd create another array of integers using the smallest unsigned integer datatype that will hold the number of unique vertices you have. In other words, if your vertex array has less than 256 vertices, then you would create an array of GLubytes, if it's more than 256, but less than 65,536, use GLushort. You build your triangles (or other shape) in this second array by referring to the vertices in the first array by their index. So, if you create vertices array with 12 vertices, then you refer to the first vertex in the array, you refer to as 0. You submit your vertices exactly the same way you did before, but instead of calling glDrawArrays(), you call a different function called glDrawElements() and pass in the integer array.

Let's finish our tutorial with a real, honest-to-goodness 3D shape: an icosahedron. Everybody else does cubes, but we're going to be geeky and do a twenty-sided die (sans numbers). Replace drawView: with this new version:

- (void)drawView:(GLView*)view;
{

static GLfloat rot = 0.0;

// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
}
;

static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
}
;

static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
}
;

glLoadIdentity();
glTranslatef(0.0f,0.0f,-3.0f);
glRotatef(rot,1.0f,1.0f,1.0f);
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);

glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
}

lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}


Before we talk about what's going on, let's run it and see the pretty shape spin:

iPhone SimulatorScreenSnapz001.jpg


It's not completely 3D looking because there are no lights and even if we had lights, we haven't told OpenGL what it needs to know to calculate how light should reflect off of our shape (that's a topic for a future posting - but if you want to, you can read some existing posts on the topic here and here).

So, what did we do here? First, we created a static variable to track the rotation of the object.

    static GLfloat rot = 0.0;


Then we defined our vertex array. We did it a little differently than before, but the result is the same. Since our geometry is not changing at all, we can make it const rather than allocating and deallocating memory every frame, and provide the values between curley braces:

    static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
}
;


Then we create a color array the same way. This creates an array of Color3D objects, one for each of the vertices in the previous array:

    static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
}
;


Finally, we create the array that actually defines the shape of the icosahedron. Those twelve vertices above, by themselves, don't describe this shape. OpenGL needs to know how to connect them, so for that, we create an array of integers (in this case GLubytes) that point to the vertices that make up each triangle.

    static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
}
;


So, the first three numbers in icosahedronFaces are 1,2,6, which means to draw a triangle between the vertices at indices 1 (0.850651, 0, 0.525731), 2 (0.850651, 0, 0.525731), and 6 (0.525731, 0.850651, 0).

The next chunk is nothing new, we just load the identity matrix (reset all transformations), move the shape away from the camera and rotate it, set the background color, clear the buffers, enable vertex and color arrays, then feed OpenGL our vertex array. All that is just like in some of the earlier examples.

    glLoadIdentity();
glTranslatef(0.0f,0.0f,-3.0f);
glRotatef(rot,1.0f,1.0f,1.0f);
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glColor4f(1.0, 0.0, 0.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);


But, then, we don't draw glDrawArrays(). We call glDrawElements():

    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);


After that, we just disable everything, and then increment the rotation variable based on how much time has elapsed since the last frame was drawn:

    glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
}

lastDrawTime = [NSDate timeIntervalSinceReferenceDate];


So, remember: if you provide the vertices in the right order to be drawn, you use glDrawArrays(), but if you provide an array of vertices and then a separate array of indices to identify the order they need to be drawn in, then you use glDrawElements().

Okay, that's enough for today. I covered a lot more ground than I intended to, and I probably got ahead of myself here, but hopefully this was helpful. In the next installment, we go back to conceptual stuff.

Please feel free to play with the drawing code, add more polygons, change colors, etc. There's a lot more to drawing in OpenGL than we covered here, but you've now seen the basic idea behind drawing 3D objects on the iPhone: You create a chunk of memory to hold all the vertices, pass the vertex array into OpenGL, and then tell it to draw those vertices.



96 comments:

abraginsky said...

Excellent series. I look forward to seeing what else you cover.

Charles said...

Hello! I purchased the iPhone development book and I found it invaluable, so I decided to look for your blog - and surprise! I found these amazing OpenGL tutorials, which is something I needed to work on.

I wanted to ask you if you have any suggestion also for pre-made engines for the iPhones like Unity or Oolong that we might use when we need to develop more complex apps and need to cut development times - I took a look at Unity, but what annoys me is that it does not seem to be specifically based on Objective-C, while I'd rather focus on Objective-C for development instead of a scripting language. Plus, it is not open sopurce. Oolong seems based on Objective-C, but I have not heard much about it.

Any personal opinion on this from you would be highly regarded by my team!

Thanks,

Rick

Jeff LaMarche said...

Rick (or is it Charles?)

I don't have an opinion on the various engines out there, to be perfectly honest. I've heard good things about the Cocos2d project, but I think that's only for 2D games, and about Sios2. Unity is a cross-platform game engine, so I can sort of understand why it doesn't use Objective-C.

I haven't heard anything bad about any of them, but I've only heard a little bit of good. This just isn't an area I've looked seriously, so am just not in a position to give an opinion, sorry to say.

Jeff

Mike said...

SIO2 is really nice and getting better every release. Highly recommended. If you're looking for 2D, cocos is superb.

Unity has some quirks that caused me to look elsewhere. The biggest quirk is the incredibly large file size for apps. You don't stand a chance staying under the 10 meg limit for 3G App Store downloads. The cost was another inhibitor for Unity. There are some pretty nice games made with it so it can be done...

Charles said...

Jeff, thanks for the reply. You cannot imagine how useful your book and this blog has been for us.

By the way, I am Rick - sorry for the "Charles" in the name but I was logged in with our office's gMail account.

Jaime said...

Wow, this is really something! I just wanted to say thanks because this is very interesting for me and I'm having trouble getting information that I understand.
Thanks again!

Jeff LaMarche said...

Jaime:

Oh, believe me, I know. This stuff is hard, I've been struggling for a while. I'm still struggling to learn it, to be honest.

Jamie Hill said...

Great articles, I have been struggling the past couple of weeks with OpenGL basics on the iPhone.

Is there any reason for not using GL_TRIANGLE_STRIP in conjunction with glDrawElements?

Jeff LaMarche said...

Jaime:

Nope, no reason - you can use all the same drawing modes with glDrawElements() that you can with glDrawArrays() - I just didn't feel like calculating the triangle strips for an icosahedron. :)

Peter said...

Hi Jeff,
First of all, THANKS! Not only is this a great intro to OpenGL ES, it's also a great intro to many 3D programming concepts that many tutorials seem to gloss over. Thanks for the awesome resource!

I have many such "simple" questions I could ask, but instead I'll just be keeping a close eye on your blog as I'm sure you're cover most of them in this series or others.

However... I'm having a problem running a project based off your "Empty OpenGL ES" template. I'm new to both CocoaTouch and OpenGL programming but I'm pretty comfortable with Obj-C and Cocoa but I can't figure out what is keeping this thing from running.

I started with your template and smiply added the triangle drawing code to drawView: but nothing happens. I narrowed the problem down to this line in GLView.m:

context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

context is nil after this call. Which makes initGLES return nil, which causes initWithFrame to return nil so way back in AppDelegate it attaches a nil view to the window. Which, of course, means absolutely nothing happens.

I don't see any errors in the console or anywhere else and, being so new at this, I have no idea why the EAGLContext alloc and init is failing... Any help or advice would be almost as greatly appreciated as this article is!

Thanks again, and I'm looking forward to the next installment.

Rodrigo Camargo said...

Hi Jeff, I purchased your book and I'm trying to figure it out how to develop games over iPhone SDK. Thanks for this tutorial. It's very useful and hard to find a high quality "how-to" like you did. I'm a brazilian Java developer (living in Sao Paulo) trying to change my mind and build more fun programs. Please, keep feeding this blog with 3D programming. Thanks again!

Jeff LaMarche said...

Peter -

I have no idea from your description. If you want to send me the xcode project, I'll be happy to take a look at it.

(If you do, delete the /build folder first, it dramatically decreases the size).

Jeff

zariok said...

I stumbled upon "part 1" while looking for iPhone OpenGL ES examples. Unaware you were one of the authors of the book "Beginning iPhone Development" until I added the RSS feed and saw the "book sighting" post.

Please keep up the series on the iPhone OpenGL ES work.

Thanks!

John D.

Greg said...

Hey Jeff,

Just wanted to say thanks a bunch for these wonderful tutorials... they've been enormously helpful*.

Coming from a non-coding background I can hardly believe I already have a custom object flying around my screen.

Please keep'em coming (when you have the time, of course : )

Greg

*as has your 'Beginning iPhone Development' book.

Glenn said...

FWIW, the April issue of MacTech magazine (received in the mail only today) has a fairly lengthy article on the Unity engine.

K. A. Barber said...

First of all, great post!
I've been developing with OpenGL on different platforms since the late 90s but only in commercial non-entertainment projects where it is used to render the main "view" and the OS provided UI elements and dialogs are used to manipulate data in the model that feeds the renderer.

My question is about the best way to structure an application (controllers/views etc...) for using opengl on IPhone while still using different views for settings/playing movies/writing text.

The project provided by Apple assumes only 1 view will be created and doesn't provide/follow the controller pattern that is suggested in the documentation. Is this hint that the controller pattern shouldn't be used for openGL apps? Are they expecting everything (text boxes, buttons, combo boxes etc) be rewritten in opengl on a per product basis?

I kinda took the Apple template and morphed it to what I thought it should "look" like, hooked a simple scene graph up to it and then saved it away to use in future projects. I noticed your sample project kinda does the same thing except cleaner/lighter than what I did. Is this what you use for your IPhone applications or do you revert back to the Apple OpenGL template for serious projects.

I am probably being over concerned about these things but I like use Best/Common practices on the basic/boiler plate stuff and only blaze a new trail when the problem domain for the app requires it....

Thanks for your reply in advanced..

Jeff LaMarche said...

K.A. Barber:

I do not revert to the Apple template. Mine is based on Apple sample code not the existing template, but reorganized.

I do not know if this is the "best" way to organize an application, to be perfectly honest. It seems a tad more logical to me and fits better with my view of MVC than either Apple's OpenGL template or any of their OpenGL sample code, and it seems to work well for single-view application. It's certainly possible that there are better ways, though, and if I find or hear of one, I'll make sure I update the template and write up a post about it. For now, that template is what I use as my starting point for pretty much all OpenGL apps.

I haven't done much with multi-view OpenGL. I know that mixing OpenGL and regular views can cause performance problems, but I don't yet have any real world experience to draw on for mixing views.

You might want to try posting your questions someplace like iphonedevsdk.com's forums, or someplace else where you can pull on a lot of different people's experiences, especially if you have specific multi-view scenarios you're thinking about using.

nesium said...

thank you for your work. this is a truly great series! i'm looking forward to the next part.

Jeremy W. said...

Your posts do a much better job of conveying the reason behind the calls than the OpenGL ES purple/gold book (1.x vs. 2.x considerations aside), which is too detailed to be of much use to a newcomer.

Your explanation of using glEnableClientState/glDisableClientState immediately brought to mind the grand HLock()/HUnlock() "oops" that Apple points out in TN1122: Locking and Unlocking Handles.

The issue is that enabling/disabling state does not nest: if some code that has enabled some state calls into your code which also enables, then subsequently disables, that state, the calling code is broken when your procedure returns.

I do not know to what degree this affects OpenGL development; my guess is about the same as the HLock issue affected Classic MacOS development, which is: subtly and infrequently.

The solution there was to save the old value of the state, set your desired value, then restore the original value when you are done. I imagine you could apply much the same technique in OpenGL ES, and that not doing so could, one of these days, when you least expect it, trip you up.

Could the on/off, rather than stack-like, behavior of glEnable/DisableClientState be a problem for this sample code in another, more complex setting?

ScottYelich said...

I'm also not getting anything on the screen (3.0b4).

Also, the drawView line has a trailing ; ... but even with that erased, the screen is just white (it's not even 0.7 grey/white, I changed to all red and screen was still white). The code *is* getting called.. but the screen is not updating.

With a build under 2.2.1 or prior, the screen does get updated. What's interesting is that if the app is closed, you see the last screen as it should have been ... zoom/shrink away -- so the drawing seems to be happening, it's just not getting updated.

Gillyd said...

Hi Jeff,
I am an avid and happy reader of your Blog and book. By far the best material out there.

Quick question about the openGL stuff:
How would I go about layering an openGL view above a regular view?
In other words, I have a full program that doesn't use openGL, but in one window I need a part that is in openGL. I want to have the openGL part layered above the rest of the program with a clear background. So far, no matter what I've tried, the glView keeps a black background.
Any tips ?

Thanks!
Gilly.

Greg said...

Hi Jeff,

Just noticed a small error? in the draw a triangle code.

glDrawArrays(GL_TRIANGLES, 0, 9);

Shouldn't that 9 be a 3?

Interestingly it still seems to work... does this mean the triangle is being drawn 3 times as the code loops through Triangle3D..?

Spaced Cowboy said...

The previous poster seems to be correct. I was seeing the same as Scott Yelich (a white background, irrespective of the clear-colour, and the triangle only visible as the app cleanly terminated).

Checking the docs for glDrawArrays(..), the last parameter is the number of indices, not the number of vertices. As soon as I changed the 9 to a 3, I see the triangle in red as expected.

What's weird is that I actually had it working earlier in the week, I guess it's just which random garbage is in RAM that we end up drawing on-screen if the number is too high...

w j said...

Awesome introduction to OpenGL ES. Straightforward examples and detailed explanations is what I've been looking for. I use Blender to create 3D objects. Do you have any recommendations as to how I might go from a Blender object to the necessary OpenGL ES arrays/structures along with how to apply the textures to the 3D object?

Glenn Marshall said...

Jeff, thank you for this life saving blog.

I've spent the last year developing my own programmed/algorithmic animation using Processing/Java - and now trying to 'migrate' it into some iPhone apps. More info on this on my website www.butterfly.ie

It's all very daunting, but your OpenGL ES posts are just what I need at the minute.

Anyway, a lot of what I do involves drawing growing vines/tendrils.

I've got the gist of the code for drawing polygons okay, but I just need to know where to start writing my own algorithms to draw polygon strips - should I put everything inside the drawView function? Any help and general pointers welcome, from anyone!

Glenn

Paul said...

Excellent blog and a complete eye opener on how to use OpenGL ES!

One question is there a knack to know how to create shapes from vertices?

I understand the use of elements etc but do not know how to create the shapes by know which vertex to reference?

Are you able to give me a pointer?

Thanks

Nicolas Goles said...

Hi there, thanks for this tutorials!

I wanted to ask you,

Your examples work perfectly in OS 2.2.1 , but in OS 3.0 (at least in beta 5), I get a blank screen, but when I press the home button, I get to see the red triangle there for a few seconds. Have you tried this ?

It would be amazing if you could update your template for OS 3.0

Thank you again , and hope you can answer my question :)


-Nicolas

Rudif said...

Hi Jeff

Just starting the OpenGL Es journey, thank you for the pointers.

I have a little problem running the second square drawing example ("re-written to allocate the two triangles on the heap") on Simulator 2.2.1 : if I grab the simulator window near the top and move it around the screen, an additional swath of red may be drawn - different ones ar different times.

If you, or other followers, can confirm this problem, any idea on what would cause it ?

Rudi

Rudif said...

Hi Jeff

This is an update for my first post.

Following the suggestion by Spaced Cowboy, in the second square drawing example I changed the last argument from 18 to 6 :
glDrawArrays(GL_TRIANGLES, 0, 6);
and this fixes the problem.


Similarly, in the third square drawing example (using triangle stripes), changing the arg value from 12 to 4 fixes a similar problem.

As a sanity check, in both above examples, changing the last argument value to 3 draws an equilateral triangle - the top half of the square.

The OpenGL ES specification applicable to iPhone 2.2.1 and 3.0 appears to be at
http://www.khronos.org/opengles/sdk/1.1/docs/man/
(following the link form Apple's page http://developer.apple.com/iphone/library/documentation/OpenGLES/Reference/OpenGLES_Framework/index.html).

Jeff, could you please confirm that this is the correct link ?

Rudi

akfaka said...

Hello Jeff,

As always, I can't tell you how much I appreciate your works in teaching newbies like me.

I have a question about the indices for glDrawElements, in this codes, the vertices is pretty simple so it is easy to create the indices for the vertices by hand, but what if i have an object has hundreds of vertices? How can I create an indices for that?

Thank you.

glennmarshall said...

just discovered the index/vertices issue myself - will cause flickering/bugs and a lot of pain and confusion, I should have read through all the comments here first!

Niklas said...

this works fine in 2.1. but when I try and run it in os 3.0 it's just a white screen? Is your template working with 3.0?

Thanks,

Niklas

David said...

Thanks for your template and tutorials. They make it so much easier to get something up and running in OpenGL ES.

I got the vertices / index problem too, more obviously when I changed GL_TRIANGLES to GL_LINE_LOOP, and a line was drawn through (0,0,0). It looks like any vertices not within the array get a value of (0,0,0) (most of the time).

Dustin Tigner said...

First, thank you very much for providing these lessons! I too didn't know you were the author of Beginning iPhone Development until after the first article. Both the book and these articles are very helpful.

@Niklas: I'm having the same problem. I tried to test the first part of this lesson on OS 3.0 and it only displays a blank screen. When I end the app, it'll flash the triangle, though that's all. I'm interested in what the fix would be, as I'm a bit too new to figure it out myself. :)

Thanks for the tutorials!

JTW said...

I've managed to bypass theV3.0 problem with the blank white screen.

In the applicationDidFinishLaunching method of the openGlEsAppDelegate class there is a line that allocates a new UIWindow to the window IVar. Comment this out and Ta Da! it works.

I found this after much searching the Dev Forums. See this entry if you have access OpenGL Application works with 2.2.1 but not with 3.0

Apparently V2 of the SDK was more forgiving.

glennmarshall said...

thanks JTW for that fix, very handy.

GregP said...

Great job Jeff! Thanks JTW for the fix for 3.0 Works fine now.

-Greg

rbthomasc said...

I just found your blog, thank you, great info. A couple of comments,

I'm just starting off with Xcode and iPhone development (I am familiar with OpenGL from another language), so maybe this is too easy.

* In your first blog, a brief explanation of using templates in Xcode along with a screenshot would make things a bit clearer. It took me a few minutes to see your template in the dialog and figure it out. :-)

* I am unable to run the template in simulator mode, the console indicates an unknown error. Am I missing something?

* I am able to run the projects on my iPhone Device 3.0. When I run the first code posting in blog two, using triangle strips and drawing a square, if I touch my screen I am getting a few drawing artifacts. e.g. a red line.

Thank you again.

LakshmiKanth.......... said...

Hello , its amazing tutorial, we all need to know that this fruit has come to us with a lots and lots of effort JEFF LAMARCHE, thank you jeff thanks a lot for your effort it is really useful for me to know about OpenGL ES.

rbthomasc said...

If you need a project to work on, how about how to draw a skybox? :-)

skube said...

Can't seem to even get the blank template running. It seems to build ok, but there is an error when using the simulator: Failed to launch simulated application: Unknown error.

Brian said...

Thanks for the excellent tutorials. I have been trying to make sense of opengl es and this is the first resource that I have found that works. Covering ES and intro to open gl in general is such a help.

Nguyen said...

Hello!

Thanks for your posting.
I am a newbie iPhone game developer. Your posting is really helpful for me. I ran your code sample above with leak instrument tool and i found out leak memory. What's happen? having bugs in your code sample or bugs cause in OpenGL ES.

Sorry for my bad English. English is only the second language.

Thanks,
Nang Nguyen.

osomer said...

Jeff, thanks for the great work. I've done a couple iPhone applications this year and have a couple ideas for games that I want to try out that may require OpenGL ES. Your tutorials have been my best resource in wrapping my head around how OpenGL ES works and how I can wrangle it to do what I want.

That is all. Just wanted to say thanks.

Pankaj said...

HI,
This might sound a novice question, But how GLView is associated with its Controller. The initMethods are having references to the GLView not the coltroller. If i want to write a new Controller . How do i make sure that my new Controller drawView is called. Please advice

Byron said...

HI, im not sure if you will still notice this post? these are great tutorials, i am very new to programming and i have recently taken a course on objective c.
I am comfortable with the syntax of objective C yet openGL i believe is plain old C? correct? as i havent had any use with C i am finding it hard to understand some of the terminolgy used, i am able to work it out a little as im using it a few times.
For openGL do i need to learn all of C and be comfortable with it or are there simply a few terms i need to get to grips with?

Thanks again
Byron

wolfscliff said...

Byron wrote
"For openGL do i need to learn all of C and be comfortable with it or are there simply a few terms i need to get to grips with?"
C is a "close to the bare metal" language, worth knowing for its simplicity and conciseness. There are plenty of reference documents and tutorials on the web, spending some time perusing them might be a worthwhile investment.

Byron said...

Thanks Wolfscliff, i shall take your advice and start by learning C, cprogramming.com seem like a good start.

rosy said...

Excellent blog! web application

Vargo said...

Thank you for these tutorials; these are my primary source for OpenGL ES information on the iPhone. I have a question though: How would I get the time delta between calls to drawView? In games, it's important to use this delta in physics calculations, but I'm not sure the best way to get this.

Thanks!

Roy Smith said...

Excellent tutorial - thanks.

A technical question:

Does GL_TRIANGLE_STRIP adhere to the "clockwise triangle = hidden face" standard? Your illustration (and the code) uses one counter-clockwise and one clockwise triangle.

Thanks, Roy

Ryan Booker said...

@Vargo

The time delta is calculated at the end of the tutorial, where the rotation is being updated.

Niklas said...

This is a great tutorial, much better than the docs from Apple! I have one question, I did everything above using your template. Then I tried to put it into the template provided by Apple, I did get it working on my 3G phone (haven't tried on 3Gs), but the performance is poor compared to using your template. The 20-sides dice is not rolling smothly, kind of "hacking" (sorry, couldn't find a good English word). Have you tried it out? What things can be the reason for this kind of performance problems? The only thing I know I didn't do that you did is this row: [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
. I assume not doing this may give some performance problems, or couldn't it? Thanks for a great site!

Eric said...

Thanks, Jeff, for the time and effort that you have but in to this tutorial.

I feel like a cad asking for yet more, but here goes. You write "Don't worry about alpha for now..." and I wonder if you are going to write more about alpha channels - even in just very simple contexts, like drawing translucent lines (arrays of vertices). Similarly, any strategies for anti-aliasing of 2D lines (which seems to have been dropped in OpenGL ES) would be very interesting.

As I have tried to transition from Quartz to OpenGL, I have found your chapter 12 "Drawing with Quartz and OpenGL" to be the most useful resource.

Thanks,
-Eric

David said...

Thank you so so much for your awesome explanations! I am very new to iPhone development, and your tutorials have helped me a ton!

I have a quick question if you do not mind.

Should the majority of the drawing be in the drawView function if we are going to keep using this code as a template? It seems like this function is what is going to be considered the "game loop" if you know what I mean. If I am unclear, please let me know, and I'll explain myself better.

Thanks again!

akhil said...

Thanks Thanks Thanks....and many thanks
Best openGL ES tutorial till now i have seen...

AHAIN THANKS :-) :-) :-) :-)

david said...

Hey, I really like it, and am trying to build a cube.
how do i implement this?
What ive got so far:

- (void)drawView:(GLView*)view;
{

static GLfloat rot = 0.0;

// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, 0, 0}, // vertices[0]
{1, 0, 0}, // vertices[1]
{1, 1, 0}, // vertices[2]
{1, 0, 1}, // vertices[3]
{0, 0, 1}, // vertices[4]
{0, 1, 1}, // vertices[5]
{0, 1, 0}, // vertices[6]
{1, 1, 1}, // vertices[7]
};

static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
};

static const GLubyte cubeFaces[] = {
0,0.5,1,
0,1,0.5,
1,0.5,0,
1,0,0.5,
0.5,1,0,
0.5,0,1,
};

glLoadIdentity();
glTranslatef(0.0f,0.0f,-3.0f);
glRotatef(rot,1.0f,1.0f,1.0f);
glClearColor(0, 0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);

glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_BYTE, cubeFaces);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
}
lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}


Thanks!!!!!!!!

leeg said...

Hi Jeff,

I've just started working through this series, so far it's going well. Thanks for some easy-to-understand information on OpenGL ES! I have the same question as Roy Smith asked: how does the winding rule work with triangle strips? It looks like vertices 1-2-3 and 2-3-4 form triangles with opposite normal vectors.

By the way, I chose to put your project template in ~/Library/Application Support/Developer/Shared/Xcode/Project Templates/Application/ so I don't have to diddle with the contents of /Developer.

Roy Smith said...

Hi leeg,

I found out the answer (see the Wikipedia entry on "Triangle Strip" and look at the OpenGL implementation comment - it differs from Wikipedia's example at the start of the article). Subsequent triangles are defined with the common side in the opposite direction. So they all end up with the same rotation.

For example, in OpenGL, it appears that: ABCDEF specifies ABC, CBD, CDE, EDF.

Note that this alternating pattern can cause a reversal if you chop the head off of the strip. Try removing A and seeing what happens. Flipping the order may also have this effect, depending on how many vertices are specified.

Cheers, Roy

Mikkel said...

truly amazing series of tutorials...

i have read some open gl before, but now some of thi is starting to make sense.....

however...just tried this in the ipad simulator and it seems like the origin is a little of the right side...

i have been lopokiong through the code but cant seems to find where i can fix this??

thanks

Carl Johan Ström said...

David:

Here is a array for a cube that works. Note that it must consists integers that designates one of the previously defined vertices.

static const GLubyte cubeFaces[] = {
6,1,0, // front side, triangle 1
1,2,6, // front side, triangle 2
5,3,4, // back side, triangle 1
3,7,5, // back side, triangle 2
0,6,4, // left side, triangle 1
4,5,6, // left side, triangle 2
1,2,3, // right side, triangle 1
3,7,2, // right side, triangle 2
6,2,5, // top, triangle 1
2,7,5, // top, triangle 2
0,1,4, // bottom, triangle 1
1,3,4 // bottom, triangle 2
};

Also you must change the following line (change 18 to 36 so that all triangles gets defined):

glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, cubeFaces);

manuel said...

Hi, very nice tutorial!!!
But the project template doesn't work for me.
I only see a black screen with the status bar on top. I'm using Xcode 3.2.2 with iPhone SDK 3.2 and run the app in the simulator with 3.1.3 OS.
Any ideas whats going wrong?

regards in advance!

Bart said...

Hi! Great tutorial but when I past the template into the folder, add that code and run it, it gives me the following error:

"Interface Builder is unable to open documents of type iPad XIB."

I'm using XCode 3.2.1 64-Bit Edition.

Thanks!

Musica con reciclaje said...

"Interface Builder is unable to open documents of type iPad XIB."
Its simple, just delete the fileÑ MainWindow-iPad.xib in the Resources Ipad folder.
It works for me

Dave Stampf said...

Hi,

Let me add my appreciation for this series. I have a question and a very strange problem with the first program.

First, the question. I type in the code exactly as presented in the blog post and see exactly the image I expected. Then, looking over the code, it seems to me that the "winding" is clockwise. If I reverse or permute the vertices in any way, I still see the bright red triangle. Shouldn't I have seen something different if looking at the inside/back face?

Now the puzzler. In permuting the vertices, I made a typo and have some very puzzling results. Here are 4 very strange cases:

// first a segment that is totally normal

Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);

// next, I mistakenly pass vertex1 3 times to the Triangle3DMake function

Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex1, vertex1);


// and what I see is... the exact same triangle - how did it even know the other two points. I did get two compiler warnings noting the unused vertexes and through using the debugger and printing out the values of the arrays in the triangle, I'm as certain as I can be that I'm passing it 3 of the same vertexes. But just to be safe, I comment out the other two as...

Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
//Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
//Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex1, vertex1);

// nothing is displayed - just what I would expect. If I uncomment them again, the triangle shows up.

Any ideas?

Thanks

Dave

alex said...

Aussome stuff Jeff, no doubt about it !

I have a question that can't seem to find a way to formulate on the search engines, so, I'll borrow from your example... I was coming along (almost) happy when you presented the "closing example", with 20-face.
I am trying to figure how the "Vertex3D vertices" came to be, ie., what is the reasoning behind all the decimal places ??
Yes, I do realize that they will be "units" but, how did you conclude that "those particular" numbers are good ??? Why not use multiples of intergers to make it easy to figure out ?

Jeanne said...

I too am having problems when running on the iPhone using 3.1.3, get a black screen, exit and then bring the app up again and it works.

Love the tutorials, haven't used openGL in years, and this is bringing it all back for me.

-jeanne

sriggins said...

I wish I had found this two years ago. Excellent tutorials. I have two bouncing die now :)

David said...

This is great stuff. Thanks.

But I am getting a compiler error from the start.
I used your empty starter project and added the
drawView method you provided.

The complier error is:
No architectures to compile for (ARCHS=, VALID_ARCHS=i386 ppc ppc64 ppc7400 ppc970 x86_64).

I cannot find the file in which the error occurs.

I am running OSX 10.6.3 with the iOS4.0 iPhone SDK.

Do you know where the error is coming from?

David said...

Ok, I found the problem with the compiler error.
The project template was not reading in my current SDK files because the template project file has a path reference to an older iphone os than what I have installed.

I opened the project package contents "project.pbxproj" file that contains all the project file definitions for startup.
I had to change the "SDKROOT" variable from iphoneos3.0 to iphoneos4.0 .

Then when I recreated a new project file from the template it would read in the SDK framework files from my current directory.

Strangely though, after the project file is made, the framework file paths in the info inspector still lead to the old 3.0 path and the framework API names are in red.

Even so, the project will compile and startup in the iPhone simulator.

David said...

All the examples are clear and understandable, but I am having difficulty with the last one, the icosahedron.

I don't understand where the glDrawElements function makes the connection between the icosahedronFaces index array and the actual vertex locations in the vertices array. How does the drawing code know that icosahedronFaces[1] equates to vertices[1], and not some other vertex array?

Andrea said...

Great! David you saved my life!

Andrea said...

Great David! You saved my life!

sam said...

excellent work....amazingly simple and understandable.. you have an art to make it simple...!!

god bless u!

Logan said...

Nice tutorial!

Any thoughts on why the same code run on an iPad throws the origin quite off?

Logan said...

A fix for the strange position for the origin when running the code on an ipad:

- Open the MainWindow-iPad.xib in Interface Builder.
- Double click the "Window" object and click inside the window that opens.
- Change the size to 1024 × 768 in the inspector.
- Save.

CB said...

Jeff, what's your policy on use of the code posted here (I haven't read every single blog post, so if I've missed a relevant statement, apologies).

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

Diego said...

Hi Jeff,

your OpenGLES tutorials for iPhone are really excellent !
They starts from the 3d foundations with code examples very documented and commented as well.

I'm trying to integrate openGLES for an Augmented Reality demo using the iPhone photocamera enabled. Could it be possisble enable the transparency on the OpenGL layer to see the input from the iphone camera ? How exactly could I do that ? I mean how do I integrate the iphone camera layar as a background in the openGL layer so that I can see 3d objects moving in the "real world" ?

Thanks a lot for this awesome tutorial series !!

Diego

pecanov said...

Absolutely amazing!

teh121 said...

You're extremly good! it's more then 7 years I wrote 1st program on OpenGL and it's fisrt time I see doc simple enough for really good understanding and with right accents

Kyle said...

Great article. What if we wanted to do this on the iPad. The viewport is cropped to the iPhone dimensions. How can I make the openGL application be fullscreen on the iPad?

Hassan said...

I am using SDK 4.0 and unable to run the application with your provided template "Empty OpenGL Xcode project template".

got the following error.

[BEROR]No architectures to compile for (ARCHS=, VALID_ARCHS=i386 ppc ppc64 ppc7400 ppc970 x86_64).

Any Idea?

Mark said...

I saw this blog and said to myself, "Wow this man teaches just a great teacher or book would explain things. Why can't my book "Beginning iPhone 3 Development" explain OpenGL as good as he does. Then I realized that he wrote that book! lol

gillissie said...

The project template doesn't work as-is. After fixing some import issues so it can compile, the first drawView code indicated here doesn't draw what the screenshot shows. Only a blank screen.

Sean said...

I had to edit the project.pbxproj file and add

SDKROOT=iphoneos4.2 (change existing)
IPHONEOS_DEPLOYMENT_TARGET = 4.0; (in buildsettings)

After that, pasting in the initial drawView worked fine.

h4ns said...

I was very encouraged to find this site. I wanted to thank you for this special read. I definitely savored every little bit of it and I have you bookmarked to check out new stuff you post.

AC Milan vs Lazio Live Streaming
West Bromwich Albion vs Wigan Athletic Live Streaming
Manchester United vs Aston Villa Live Streaming
Sunderland vs Chelsea Live Streaming
Arsenal vs Everton Live Streaming
Augsburg vs Bochum Live Streaming
Racing Santander vs Valencia Live Streaming
Frosinone vs Atalanta Live Streaming
AC Milan vs Lazio Live Streaming
West Bromwich Albion vs Wigan Athletic Live Streaming
Manchester United vs Aston Villa Live Streaming
Sunderland vs Chelsea Live Streaming
Arsenal vs Everton Live Streaming
Augsburg vs Bochum Live Streaming
Racing Santander vs Valencia Live Streaming
Frosinone vs Atalanta Live Streaming
Technology News | Hot News Today | Live Stream

Adam said...

Wow. This is exactly what I'm looking for. But it seems like the template project needs updating in order to work. I'm too much of a beginner to be able to figure out how to tweak it on my own. Any chance this will be updated?

Jenox said...

Hey Jeff,
first of all, I'd like to thank you for this beautiful series of tutorials! They really helped me a lot.

But there's a bug in this tutorial (and other ones probably, too).
When using glDrawArrays() to draw a triangles or a triangle stip, you don't have to pass it the amount of float values as the third argument - you have to pass the amount of vertices.

I've noticed that while playing around a little, and OpenGL has drawn triangles from my vertex array although I did not want it to draw them.
I almost went mental, but after many hours I finally found the solution...

Greetings, Jenox

Pranay airan said...

Agree with jenox i encounter the same thing when i was drawing the square example it was drawing some weired things, i just did a trial and error and found out that you have to pass the amount of vertices.
anyways nice tutorial

Pranay

ProgrammerInProgress said...

This is an excellent tutorial, I'm currently doing a rather high-paced crash course in OpenGL at uni and some things have been skimmed over due to a lack of time, this reference is absolutely invaluable to me and It's a very good read indeed.

Safwan Ahmed said...

Hi Jeff,
That's a wonderful and very helping tutorial. Thanks for that. Please let me know can I use this code in my App and can publish that?
Thanks

Jeff LaMarche said...

Safwan:

Unless I specifically mention a license, you can assume that any code I post is okay to be used in your projects.

Jeff

mapler531 said...

I'm having a problem with the first step. I copied everything down, but when I run it I get the following build error:

No architectures to compile for (ARCHS=, VALID_ARCHS=i386 ppc ppc64 ppc7400 ppc970 x86_64).

Does anybody know how to fix this?

Niklas Björnberg said...

I realize of course that it is very hard to answer questions pertaining to specific bits of code, since my setup might differ in some unknown way from the one you used. However, I am so puzzled by the behavior I experience that I had to ask just for a chance to still my curiosity.

If I add another set of vertices in the very first drawing-a-triangle example, like this...

Vertex3D vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
Vertex3D vertex4 = Vertex3DMake(0.0, 1.0, -3.0);
Vertex3D vertex5 = Vertex3DMake(1.0, 0.0, -3.0);
Vertex3D vertex6 = Vertex3DMake(-1.0, 0.0, -3.0);
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);

(all other things left untouched, apart from using the XCode 4.0 template supplied elsewhere in this blog)

... the final image contains the expected triangle but also a weird other field of red stretching out in seemingly infinite areas towards the negative x. The really mind boggling thing is that I have not changed the call to glvertexPointer or glDrawArrays, so afaik OpenGL should not know about these extra vertices.

I'm really confused by this. Is there a logical explanation to this or should I check for stack overflows or something to that effect? I would not really expect the stack to be that small... uuh huh.

This mystery besides: great posts. I have been reading them with plesaure. :)

Niklas Björnberg said...

Ah, my best shot at the logical explanation:
As other comments point out, the last parameter in glDrawArrays(..) seem to be not the number of points but the number of vertices. Setting it to 3 normalizes the situation (so I guess my coding is 1 unit excellent now, hehe (far fetched pun intended)). I guess that it drew "random" stack content previously.

BR, Niklas