Friday, April 17, 2009

OpenGL ES From the Ground Up, Part 1: Basic Concepts

I've done a number of postings on programming OpenGL ES for the iPhone, but most of the posts I've done have been targeted at people who already know at least a little bit about 3D programming.

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/
There are a number of good tutorials and books on OpenGL. Unfortunately, there aren't very many on OpenGL ES, and none (at least as I write this) that are specifically designed for learning 3D programming on the iPhone. Because most available material for learning OpenGL starts out teaching using what's called direct mode, which is part of the functionality of OpenGL that's not in OpenGL ES, it can be really hard for an iPhone dev with no 3D background to get up and running using existing books and tutorials. I've had a number of people request it, so I've decided to start a series of blog posts designed for the absolute 3D beginner. This is the first in that series. If you've read and understood my previous OpenGL postings, you will probably find this series to be a little too basic.

OpenGL Datatypes


The first thing we'll talk about are OpenGL's datatypes. Because OpenGL is a cross-platform API, and the size of datatypes can vary depending on the programming language being used as well as the underlying processor (64-bit vs. 32-bit vs 16-bit), OpenGL declares its own custom datatypes. When passing values into OpenGL, you should always use these OpenGL datatypes to make sure that you are passing values of the right size or precision. Failure to do so could cause unexpected results or slowdowns caused by data conversion at runtime. Every implementation of OpenGL, regardless of platform or language, declares the standard OpenGL datatypes in such a way that they will be the same size on every platform, making porting OpenGL code from one platform to another easier.

Here are the OpenGL ES datatypes:
  • GLenum: An unsigned integer used for GL-specific enumerations. Most commonly used to tell OpenGL the type of data stored in an array passed by pointer (e.g. GL_FLOAT) to indicate that the array is made up of GLfloats.
  • GLboolean: Used to hold a single boolean values. OpenGL ES also declares its own true and false values (GL_TRUE and GL_FALSE) to avoid platform and language differences. When passing booleans into OpenGL, use these rather than YES or NO (though it won't hurt if you accidentally use YES or TRUE since they are actually defined the same. But, it's good form to use the GL-defined values.
  • GLbitfield: These are four-byte integers used to pack multiple boolean values (up to 32) into a single variable using bitwise operators. We'll discuss this more the first time we use a bitfield variable, but you can read up on the basic idea over at wikipedia
  • GLbyte: A signed, one-byte integer capable of holding a value from -128 to 127
  • GLshort: A signed two-byte integer capable of holding a value between −32,768 to 32,767
  • GLint: A signed four-byte integer capable of holding a value between −2,147,483,648 and 2,147,483,647
  • GLsizei: A signed, four-byte integer used to represent the size (in bytes) of data, similar to size_t in C.
  • GLubyte: An unsigned, one-byte integer capable of holding a value between 0 and 255.
  • GLushort: An unsigned, two-byte integer capable of holding a value between 0 and 65,535
  • GLuint: An unsigned, four-byte integer capable of holding a value between 0 and 4,294,967,295
  • GLfloat: A four-byte precision IEEE 754-1985 floating point variable.
  • GLclampf: This is also a four-byte precision floating point variable, but when OpenGL uses GLclampf, it is indicating that the value of this particular variable should always be between 0.0 and 1.0.
  • GLvoid: A void value used to indicate that a function has no return value, or takes no arguments.
  • GLfixed: Fixed point numbers are a way of storing real numbers using integers. This was a common optimization in 3D systems used because most computer processors are much faster at doing math with integers than with floating-point variables. Because the iPhone has vector processors that OpenGL uses to do fast floating-point math, we will not be discussing fixed-point arithmetic or the GLfixed datatype.
  • GLclampx: Another fixed-point variable, used to represent real numbers between 0.0 and 1.0 using fixed-point arithmetic. Like GLfixed, we won't be using or discussing this datatype.

OpenGL ES (at least the version used on the iPhone) does not support any 8-byte (64-bit) datatypes such as long or double. OpenGL does have these larger datatypes, but given the screen size of most embedded devices, and the types of applications you are likely to be writing for them, the decision was made to exclude them from OpenGL ES under the assumption that there would be little need for them, and that their use could have a detrimental effect on performance.

The Point or Vertex


The atomic unit in 3D graphics is called the point or vertex. These represent a single spot in three dimensional space and are used to build more complex objects. Polygons are built out of these points, and objects are built out of multiple polygons. Although regular OpenGL supports many types of polygons, OpenGL ES only supports the use of three-sided polygon, (aka triangles).

If you remember back to high-school geometry, you probably remember something called Cartesian Coordinates. The basic idea is that you select an arbitrary point in space and call it the origin. You can then designate any point in space by referencing the origin and using three numbers, one for each of the three dimensions, which are represented by three imaginary lines running through the origin. The imaginary line running from left to right is called the x-axis. Traveling along the x-axis, as you go to the right along the x axis, the value gets higher and as you go to the left, they get lower. Left of the origin are negative x values, and to the right are positive x values. The other two axes work exactly the same way. Going up along the y axis, the value of y increases, and going down, it decreases. Values above the origin have a positive y value, and those below the origin have a negative y value. With z, as objects move away from the viewer, the value gets lower, and as they move toward the viewer (or continue behind the viewer), values get higher. Points that are in front of the origin have a positive z value, and those that are behind the origin have a negative z value. The following illustration might make help those words make a little more sense:
cartesian.png

Note: Core Graphics, which is another framework for doing graphics on the iPhone uses a slightly different coordinate system in that the y axis decreases as it goes up from the origin, and increases as it goes down.


The value that increases or decreases along these axes are in an arbitrary scale - they don't represent any real measurement, like feet, inches, or meters. You can select any scale that makes sense for your own programs. If you want to design a game where each unit is a foot, you can do that. if you want to make each unit a micron, you can do that as well. OpenGL doesn't care what they represent to the end user, it just thinks of them as units, and make sure they are all equal distances.

Since any object's location in three-dimensional space can be represented by three values, an object's position is generally represented in OpenGL by the use of three GLfloat variables, usually using an array of three floats, where the first item in the array (index 0) is the x position, the second (index 1) is the y position, and the third (index 2) is the z position. Here's a very simple example of creating a vertex for use in OpenGL ES:

    GLfloat vertex[3];
vertex[0] = 10.0; // x
vertex[1] = 23.75; // y
vertex[2] = -12.532; // z


In OpenGL ES, you generally submit all the vertices that make up some or all of the objects in your scene as a vertex array. A vertex array is simply an array of values (usually GLfloats) that contains the vertex data for some or all of the objects in the world. We'll see how that process works in the next post in this series, but the thing to remember about vertex arrays is that their size is based on the number of vertices being submitted multiplied by either three (for drawing in three-dimensional space) or two (for drawing in two-dimensional space). So, a vertex array that holds six triangles in three-dimensional space would consist of an array of 54 GLfloats, because each triangle has three vertices, and each vertex has three coordinates and 6 x 3 x 3 = 54.

Dealing with all these GLfloats can be a pain, however, because you're constantly having to multiply things in your head and try to think of these arrays in terms of the vertices and polygons that they represent. Fortunately, there's an easier way. We can define a data structure to hold a single vertex, like this:

typedef struct {
GLfloat x;
GLfloat y;
GLfloat z;
}
Vertex3D;

By doing this, our code becomes much more readable:

Vertex3D vertex;
vertex.x = 10.0;
vertex.y = 23.75;
vertex.z = -12.532;

Now, because our Vertex3D struct is comprised of three GLfloats, passing a pointer to a Vertex3D is exactly the same as passing a pointer to an array of three GLfloats. There's no difference to the computer; both have the same size and the same number of bytes in the same order as OpenGL expects them. Grouping the data into these data structures just makes it easier for us as the programmer to visualize and deal with the data. If you download my Xcode template from the beginning of this article, this data structure and the supporting functions I'm going to be discussing next have already been defined in the file named OpenGLCommon.h. There is also an inline function for creating single vertices:

static inline Vertex3D Vertex3DMake(CGFloat inX, CGFloat inY, CGFloat inZ)
{
Vertex3D ret;
ret.x = inX;
ret.y = inY;
ret.z = inZ;
return ret;
}


If you remember back to geometry (or maybe you don't, which is okay), the distance between two points on a plane is calculated using this formula:

distance formula.png


We can implement this formula to calculate the straight-line distance between any two points in three-dimensional space with this simple inline function:

static inline GLfloat Vertex3DCalculateDistanceBetweenVertices (Vertex3D first, Vertex3D second)
{
GLfloat deltaX = second.x - first.x;
GLfloat deltaY = second.y - first.y;
GLfloat deltaZ = second.z - first.z;
return sqrtf(deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ );
}
;


Triangles


Since OpenGL ES only supports triangles, we can also create a data structure to group three vertices into a single triangle object.

typedef struct {
Vertex3D v1;
Vertex3D v2;
Vertex3D v3;
}
Triangle3D;


Again, a single Triangle3D is exactly the same as an array of nine GLfloats, it's just easier for us to deal with it in our code because we can build objects out of vertices and triangles rather than out of arrays of GLfloats.

There are a few more things you need to know about triangles, however. In OpenGL, there is a concept known as winding, which just means that the order in which the vertices are drawn matters. Unlike objects in the real world, polygons in OpenGL do not generally have two sides to them. They have one side, which is considered the front face, and a triangle can only be seen if its front face if facing the viewer. While it is possible to configure OpenGL to treat polygons as two-sided, by default, triangles have only one visible side. By knowing which is the front or visible side of the polygon, OpenGL is able to do half the amount of calculations per polygon that it would have to do if both sides were visible.

Although there are times when a polygon will stand on its own, and you might very well want the back drawn, usually a triangle is part of a larger object, and one side of the polygon will be facing the inside of the object and will never be seen. The side that isn't drawn is called a backface, and OpenGL determines which is the front face to be drawn and which is the backface by looking at the drawing order of the vertices. The front face is the one that would be drawn by following the vertices in counter-clockwise order (by default, it can be changed). Since OpenGL can determine easily which triangles are visible to the user, it can use a process called Backface Culling to avoid doing work for polygons that aren't facing the front of the viewport and, therefore, can't be seen. We'll discuss the viewport in the next posting, but you can think of it as the virtual camera, or virtual window looking into the OpenGL world.

winding.png


In the illustration above, the cyan triangle on the left is a backface and won't be drawn because the order that the vertices would be drawn in relation to the viewer is clockwise. On the other hand, the triangle on the right is a frontface that will be drawn because the order of the vertices is counter-clockwise in relation to the viewer.

In the next posting in this series, we'll look at setting up the virtual world in OpenGL and do some simple drawing using Vertex3D and Triangle3D. In the post after that, we'll look at transformations which are a way of using linear algebra to move objects around in the virtual world.



56 comments:

Caleb said...

Thanks for posting this. I'm looking forward to the next article in the series. I've always wanted to learn the basics of 3D graphics, but it's always been quite intimidating. I'm sure I'll be wishing I would have paid more attention in my Linear Algebra class :)

pfhorth said...

I just started learning about OpenGL and was intending to start out with playing with it on the iPhone. I really appreciate this article and look forward to the series.

Adam Alexander said...

Agreed, thanks for this informative article Jeff! 3D programming is probably not the most intuitive skill to learn for most developers who don't have a game programming background (myself included).

Manpreet Singh said...

Jeff,

A great article. For OpenGL fundamentals, a great resource is an online version of the OpenGL "red book" - a very useful book if you're working on the fundamentals. It's been useful for me as a good starter book and also whenever I have had to brush up my fundamentals.
http://unreal.srk.fer.hr/theredbook/

Another great set of tutorials with some advanced stuff can be found here:
http://nehe.gamedev.net

OpenGL ES is different, of course, and one has to account for that. Most basically, the glu and glut calls are missing.

Here's my recent OpenGL project on the iPhone (more additions coming) :)
http://www.apprite.com/rattler

Thanks,
Manpreet.

VesperDEM said...

I hope I'm not jumping the gun here, but I'm curious what 3D editor folks use to get 3d objects in OpenGL ES. I'm not really all that good with a lot of them, but I'm kind of fond of SketchUp. I realize it's not a real 3D editor, but it's one I actually understand.

Thanks in advance!

Alex Ilin said...

Thank you, thank you, thank you

looking forward to the next article in the series

Alex said...

Thanks for doing this. This was the only thing I thought your book didn't really hit enough on, so this is a really great thing to do.

Glenn said...

> If you download my Xcode template from the beginning of this article

I looked and looked, and searched with the browser, but I cannot find it.

> three-sided triangles, (aka triangles)

FWIW, I think you meant "three-sided polygons."

Glenn said...

P.S. My earlier post sounded like I was just fault-finding. In fact, I'm hugely appreciative of your taking on this task. I still think there's a market for a book on OpenGL ES for the iPhone, particularly with all the budding game developers.

Jose Miguel said...

Incredible, i'm looking forward for the next article in the series. I also love your book, i bought it last week and i'm learning a lot.

Richard said...

This is an awesome post.

I really needed this. I was looking for a primer for OpenGL ES and could not find one on the intertubes.

Thanks so much.

If you wrote a book on this, I'd buy two.

Jeff LaMarche said...

Glen:

No worries. I've had much more brutal things said about my writing. I've corrected both problems, thanks for pointing them out.

Thanks everyone else for the kind words.

Jeff

Havoc said...

A couple of times Jeff mentions that there is a link at the top of the page for his template.

You can actually find it in part 2 of this guide. ( Look in the blog archive navigation to the right of the article in April 2009 )

Joshua said...

Hi Jeff,

First of all - thanks for the great tutorials on OpenGL ES. Definitely helping out.

This is a little out of the blue but just wondering if you could provide a little hint on a problem I'm having with my current project.

The idea is a basic 'spray painting' app where the user (as you probably would have guessed it) gets to paint with spray cans.

The approach I've taken is to take the users 'touch movements' and dot a whole lot of points (using the selected colour, texture, and distance).

The problem I have is that after a whole the performance starts decreasing significantly (I store them in an array of objects that store their vertex buffer of points and these objects are responsible for drawing themselves).

I have tried the following solutions, so far, with little luck.

1. if the 'paint stroke' is the same as the previous them merge the new points with this objects vertex points (helper but not quite).

2. rendering only the 'paint strokes' and using glCopyTexSubImage2D and store it into a texture (gives me a black background and rotates it).

3. similar to the above but using glReadPixels and creating a CGImage (similar result to the above).

Last point; the reason I'm not directly drawing to the context/buffer(?) is because I'm wanting to animate a spray can and allow the user to move along the wall.

... I know this is probably a long shot (in wanting you to answer) but you seem like a guru and I would appreciate any advice/suggestions of how I can approach this.

Cheers,

PS last resort will be using the Quartz example from your book.

Dave said...

Thank you for your tutorials. I have a question about your inline function Vertex3DMake. Are there benefits to using CGFloat instead of GLfloat, or does it not matter?

crankytech said...

Thanks for this tutorial. Unfortunately, the site holding the project template is down. Could you post it to another location?

Paul said...

Hi Jeff, I have your Beginning iPhone Development book which kicked started my iPhone development and I wanted to get into OpenGL ES but know nothing of the topic.....until I found you blog that is!

Excellent posting...thanks

Nikolaus said...

Great article - thanks for sharing…

Taras_96 said...

Great intro post!

woolyninja said...

Great start to what seems like it'll be a fantastic series of posts to get me up to speed on OpenGL ES! Very much appreciated!

sabera said...

Mathematics of Linear Algebra

julioykaly said...

Thanks for posting!

I was confused on creating vertex and such. I was trying to understand many tutorials on the web. Some where good however they assume I know 3D concepts or at least how to implement the concept into code. I do hope these tutorials will help me understand the concepts and how to translate it to code. Thank you again.

Julio A. Cruz

Jonathan said...

Can you use GLushorts for vertex data? In the examples here you mention they are usually GLfloats. In my particular project, I'm using OpenGL ES for 2D graphics and drawing individual pixels using GL_POINTS. In my case, the coordinates are always integers, but when I attempt to use GLushort I get a crash.

Is it possible to use GLushorts instead of floats?

Mazicky said...

this is amazing post !

ajeet said...

nice work

Haris Jalal - حارث جلال said...

Thank you for posting! Merry Christmas Jeff!!!!!!!

Rick said...

Typo found in 4th paragraph of Triangles. One "if" should be "is".

Change bolded:

"They have one side, which is considered the front face, and a triangle can only be seen if its front face if facing the viewer."

Ryan H said...
This comment has been removed by the author.
Ryan H said...

Jeff, I was wondering if your OpenGL ES template was updated before or after Xcode 3.2.1 (iPhone OS 3.1.2) was released. In particular, I'm looking at the way Apple's template uses separate rendering classes for OpenGL ES 1 vs. 2 as well as using CADisplayLink for the rendering loop. At the last iPhone Developers conference, it was stated that this is the preferred way for the render loop since it is synchronized to the run loop which provides better synchronization for the refresh (I don't really know the exact details but the old method can possibly still cause some flicker because of something to do with the timing of the run loop).

I do like the extra stuff in your template though such as the macros and inline functions. However, since I'm just now beginning to get into OpenGL I'm a little afraid to attempt to combine the two.

akhil said...

Thnaks, Very useful tutorial.
:-)

Axel said...

Hi Jeff!!

Congratulations for this great tutorial!!

But the link to download the template is broken, could you please restore it?

Btw, have u stop written these tutorials?

cheers!

Abhinav said...

I am new to Blender. I tried running your python script which exports to objective C header . But every time i run it it gives me an error

"ImportError: No module named Blender"

Any kind of help would be much appreciated.

ajeet said...

Please check that whether You added frameworks or not

Ankit said...

You have Done really a Good Work.I am waiting for your Next Series.Keep it up and Thanks from the Bottom of my Heart.

gg group said...

Thanq very much to provide the great article...........

ayanonagon said...

Amazing tutorial, thank you! I will be coming back regularly for more.

Bill Kidder said...

I can't use your template with
XCode 3.1.4, targeting Simulator 3.1.3:

"Internal Error:
Description: “MainWindow-iPad.xib” could not be handled because ibtool cannot open files of this type.
Failure Reason: ibtool cannot open files of this type.
"

Bill Kidder said...

I figured it out - select the .nib file in the project, GetInfo, and set its type to "wrapper.nib"

Claus Wahlers said...

If you are using XCode 3.2.3 and iPhone SDK 4.0, you might want to change the SDK in ___PROJECTNAME___.xcodeproj/project.pbxproj: search for "SDKROOT = iphoneos3.0;" and change the value to "iphoneos4.0". You'll get a "XCode Base SDK missing" error otherwise.

Edwin said...

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

Mark said...

Claus Wahlers

Where is this file? I can't find it

Mark said...

Ok so I found that line and changed the value to "iphoneos4.0" but my project still says "Base SDK Missing", any other ideas?

vividentity said...

choose Project --> Edit PRoject Settings. When a dialog appears, click the Build tab and change the value in the Base SDK option.

Second, choose Project --> Edit Active Target. When a dialog appears, click the Build tab and change the Base SDK option again.

vividentity said...
This comment has been removed by the author.
antywong said...

The rounded shape of speedy 30 features textile fake louis vuitton lining and leather trimmings with shiny Louis Vuitton Monogram ldylle Romance Encre golden brass. Sized at 11.8" x 8.3" x 6.7", the large capacity Hermes Original Python Birkin 30 Grey of this bag is enough for handbags review daily essentials; you can put bags wholesale everything into this city bag. It also fits for Hermes Clemence Jypsiere 34 Purple every occasion and perfectly goes with any outfits mfakng100910.

vikram said...

Hi
I have tried every thing but I cannot compile the template. It keep saying

No Architecures to compile for

I have xcode 3.2.3 please help

vividentity said...

@vikram please check your ACTIVE PROJECT Settings, and select the base SDK.

Please see this post
http://www.lapegna.com/2010/06/xcode-base-sdk-missing/

chao said...

Great article! Very clear

kit da shit said...

Jeff, thx from hk :)

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

TBB said...

Designer Handbags Imitations
fake name brand handbags
knock designer handbags
brand names fashion
imitations handbags
imitation purses
purse store names
top brands handbags
copy designer
buy handbags
replica handbags
high quality luxury shoes
footwear
shoe discount
heels
cheap shoes and handbags
stock shoes

guuskoning said...

I'm wondering why you use your own template and not the one given bij Xcode (OpenGL ES Application)

David said...

Thanks for the great tutorials. When is the book coming out?

Hire iphone developer said...

Hi,
Before you make any hirings, you should be well aware of all the terms and conditions laid down by the iPhone development company, along with the payment details, so that you do not have to face any problems at the later stage of development.
Thanks.

hire iphone developer

Alik said...

Hi. Have anyone figured how to install the project template on Xcode 4? Thank you!

foreverloud said...

Does anyone else have a broken link to get the link to the template? or does anyone have the file they could link to me? Thanks in advance.