Wednesday, May 13, 2009

OpenGL ES From the Ground Up, Part 5: Living in a Material World

Okay, in the last installment, we talked about lights, how to set them up and what attributes they have. We also talked about the three components of light, diffuse, ambient, and specular. If you're not completely clear on those, you might want to review, as those components are used heavily in defining materials.


Source image from Wikimedia Commons, modified as allowed by license.

As a starting point for this installment, we're going to use the Procedural Sphere project from this posting. We're switching to a sphere rather than continuing with the icosahedron that we've been using up to now because a sphere is the ideal shape for showing how the different components of lights and materials interact with each other.

What is Color

So, let's talk about what color is. This is probably a review for anybody who passed grade school art, but let's do it anyway. Why do things have color in the real world? What causes it?

Light that we can see is called the visible spectrum of light. We perceive different colors based on the wavelength of that light. At one end of the visible spectrum, we have the higher-frequency, lower wavelength purples and blues, and at the other end, we have the lower-frequency, higher wavelength oranges and reds:


Electromagnetic waves that fall outside of this range - both higher and lower - are not "visible light", though it is something ofan artificial distinction since the only difference is the wavelength, frequency, and the ability of the human eye to perceive it. However, that ability to perceive electromagnetic waves is everything, so we as far as OpenGL is concerned, the visible spectrum is all we care about.

Now, "white light" contains equal amounts of all the wavelengths. In other words, white light is white because it contains all (or at least most) of the frequencies of visible light. If you've ever experimented with a prism, you've seen this effect in action:


The prism refracts the white light so that light with different wavelengths is separated. This is also the process by which rainbows are created and ZOMG Ponies! No, not really on the ponies, but it's true about the rainbows.

Now, if you look at an object, and it appears blue, what is happening is that the object is absorbing most of the lower end of the visible range. The object is absorbing red, orange, yellow and green light. Depending on the shade of blue, it may also be absorbing violet and some blue.

But most of the blue wavelengths are being reflected back towards your eye. Because some of the visible light has been absorbed, the light being reflect back to your eye is no longer white because it no longer contains all the wavelengths of the visible spectrum.

Simple enough, right? Let's see how this applies to OpenGL.

OpenGL Materials

To define a color for a material in OpenGL ES, we define how that material reflects light, just like how things work in the real world. If a material is set up to reflect red light, then it will appear red when shown under regular, white light.

In OpenGL (at least when when using smooth shading and lights) materials don't have just a single color. We have the ability to specify exactly how the material reflects each of the three components of OpenGL lights (ambient, diffuse, and specular) separately. We additionally have the ability to specify the material's emissive property, which we'll talk about a little later.

Specifying a Material

To create a material in OpenGL, we make one or more call to either glMaterialf() or glMaterialfv(). Similar to the way we specified lights in the last installment, we often have to make multiple calls to these functions to fully define the material because each attribute or component has to be specified individually by making a separate call. Any component or attribute that is not specified defaults to zero or, in the case of colors, to black.

The first parameter passed to either glMaterialf() or glMaterialfv() is always a GL_ENUM which specifies whether the material affects the front, back, or both the front and back of polygons. There's actually no point in having this first argument in OpenGL ES other than for compatibility with OpenGL ES, because there's only one valid option: GL_FRONT_AND_BACK, which simply indicates that materials are used for any polygon that is drawn. If you remember from part 1, triangles have a front and a back that is determined by their winding (order the vertices are drawn). By default, only the front of a triangle is drawn in OpenGL, but it's possible to make OpenGL draw the backside, or even to draw only the backs, and regular OpenGL would let you specify different materials for front and back by passing either GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. But, OpenGL ES doesn't support any option other than GL_FRONT_AND_BACK, so just pass that always, 'kay?

The second parameter to glMaterialf() or glMaterialfv() is a GL_ENUM that identifies which component or attribute of the material you are setting. These are the same values we passed into glLightfv(), like GL_AMBIENT, along with some new ones you'll see in a moment.

The final value is either a GL_FLOAT, or a pointer to an array of GL_FLOATs that contains the actual value for the attribute or component.

The most imoprtant components of a material that need to be set are the ambient and diffuse components, because they define how a material reflects the bulk of the light being shined on it. The code project we're using today has defined white light, much like the light produced by the sun, or a light bulb in that it has all wavelengths or colors of light represented equally. If our light were not white, the appearance of our sphere would be different. Blue light reflecting off a red material, for example, would produce a purple shade. For simplicity, we're going to work only with white light and you can feel free to experiment with changing the colors of the lights on your own to see how lights and materials interact. For the most part, they interact in OpenGL ES the way they would in real life.

Here is what the project looks like when run, before adding any materials:

iPhone SimulatorScreenSnapz001.jpg

As you can see, we've got some ambient light and considerably more diffuse light.

Ambient and Diffuse

When talking about materials in OpenGL, we need to talk about the ambient and diffuse components at the same time because these two work together to define the perceived color of the object. Remember, diffuse light is the top part of the sphere in very first picture in this posting (the brighter yellow), and ambient is the darker yellow part on the underside. How the material reflects these two components will determine what color you perceive the object to be. Now, the picture above could have been achieved in more than one way. Likely, the yellow sphere reflects the ambient and diffuse properties in the same proportion, but the scene has less ambient light defined.

Probably 90% of the time or more, you will specify the ambient and diffuse parameters of a material exactly the same. By doing this, it becomes the amount of diffuse and ambient that determines the shading and appearance of the object. There is, in fact, a way to specify a material's diffuse and ambient components at the same time with a single call to glMaterialfv(). Here is how we might declare our material to be a shade of blue:

    GLfloat ambientAndDiffuse[] = {0.0, 0.1, 0.9, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientAndDiffuse);

Just like with glColor4f(), setting a material dictates the way everything is drawn until another material is specified. If the code above is placed before our drawing code, and we now run the project, we're going to see that our sphere has become a single shade of blue. The underside is still darker because our ambient light is not as strong as our diffuse light:

iPhone SimulatorScreenSnapz002.jpg

There may be times when you want more control and you want to specify how the material reflects the diffuse and ambient light separately. For example, we could do the following to create a material that reflects the blue from the ambient light but the red from the diffuse light:

    GLfloat ambient[] = {0.0, 0.1, 0.9, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
GLfloat diffuse[] = {0.9, 0.0, 0.1, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);

If we run this, we get a very different appearance:

iPhone SimulatorScreenSnapz003.jpg

In this case, it looks like we're shining a colored light on the sphere, even though we're not. The reason that it looks that way is that we're reflecting the directional light differently than the ambient light.

In most cases, if you want to give the appearance of colored lights, then create colored lights and specify your material colors using GL_AMBIENT_AND_DIFFUSE. But, there are times when you might want to separate them out to create special effects or to fake an isolated, colored spot light without incurring the overhead of an additional light. Remember: Every light you add increases the calculations that have to be performed every second, so it's not a bad idea to cheat sometimes.

Specular and Shininess

You can also specify how your light reflects the specular component of the scene's lights separately from the diffuse and ambient components. This gives you the ability to control how bright the specular "hot spot" is. A separate parameter, called GL_SHININESS works together with the material's specular component to define how big the specular hot spot is. If you do specify a GL_SPECULAR value for your material, you should also define its shininess. The higher the shininess, the smaller the specular reflection, so the default value of 0.0 tends to completely overwhelm the diffuse component and generally looks bad.

Let's return to our blue sphere, and add a specular hot spot to it.

    GLfloat ambientAndDiffuse[] = {0.0, 0.1, 0.9, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientAndDiffuse);
GLfloat specular[] = {0.3, 0.3, 0.3, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);

We've used a dim white for our specular value. This may seem low, but the way specular is calculated, a little seems to go a long way. The quality of the specular component of the lights is multiplied by the material's specular component, and the resulting light is concentrated in the area dictated by the material's shininess. Here is what the sphere looks like with the code above:

iPhone SimulatorScreenSnapz004.jpg

We now have a small circular area on the sphere that's reflecting more light. We can make this spot brighter by increasing either the specular component of our light or lights, or by increasing the shininesst of our material. We can also change the size of the highlight by tweaking the shininess. The shinier the material, the more concentrated the specular highlight. If we change the shininess from 25.0 to 50.0, for example, we get a much tighter hot spot, which gives the appearance of a more highly polished surface.

iPhone SimulatorScreenSnapz005.jpg

Here's a little warning, though. Part of the reason why I switched to spheres for this posting, and the reason why I used a relatively high vertex count for this sphere, is because specular highlights on low-poly objects don't look very good. Watch what happens if I reduce the number of vertices in our sphere to a more game-friendly number:

iPhone SimulatorScreenSnapz006.jpg

The specular highlights tend to make the triangle edges stand out, and often specular highlights just don't look good for low-poly objects like those you'd use in a game. In regular OpenGL, there is something called a shader that can be used to achieve good results with low-poly models, but the version of OpenGL ES on the iPhone does not have shaders. The only way you can generally make low-poly objects look good in games is by forgetting about the specular component altogether and using texture mapping, something we'll look at starting in the next installment.


There's one last important attribute of materials that we need to look at, and it's called the emission (or sometimes the emissive) component. By setting the emission component, we make the material look like it is emitting light of the color we specify. Now, it's not really emitting light. Nearby objects, for example, will not be affected by another object's emissive light. If you want to make an object like a light bulb that actually shines on other objects, you need to combine the emission component with an actual light at the same location as the object because (and this is going to sound redundantly redundant) but in OpenGL ES, only lights actually emit light. But, the emission component can be used to give objects a nice glow.

We could, for example, add a nice green glowiness to our blue sphere, by adding this code:

    GLfloat emission[] = {0.0, 0.4, 0.0, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emission);

This would give us a result that looks like this:

iPhone SimulatorScreenSnapz007.jpg

The emission component affects the entire material, so the value specified for GL_EMISSION is multiplied by any type of light that falls on a specific spot on the object. Notice that even the specular highlight in the picture above has taken on a bit of a blue-green cast rather than being pure white. It's subtle in the brighter spots, and most noticeable in the bottom area where only ambient light is being reflected, but it affects everything.

That's All She Wrote

Well, I though long and hard about some kind of Madonna-reference or at least an 80's music reference for the conclusion here to tie it together with the posting's title, but I was completely unable to come up with anything even remotely witty, so I'm going to have to just leave it here.

In the next installment, we will look at texture mapping, which can be used both for two-dimensional sprites and also for giving three-dimensional objects more complex surfaces than materials allow for.


Jan Geffert said...

Great Post!

Maybe you could change GL_FONT_AND_BACK into GL_FRONT_AND_BACK

Jeff LaMarche said...

Why would I want to do that?

Just kidding, I'll go change it, thanks!

tester said...

Jeff great Post. I am a newbie in iPhone and 2 week into OpenGL ES.

Can you recommned some books on the track of OpenGL ES.

I am doing a lot of travel and I do own a iMac at home, is there any tool(windows based) that I can try out these samples out of my laptop(wondows).

Thanks again for your great work, keep it going.

- javabuddy

Jeff LaMarche said...


Unfortunately, I don't know of a good OpenGL ES 1.1 book, and I know there's not an iPhone-specific one yet, though I know there's at least one in the works by my publisher (but not written by me).

I also don't know how you might be able to try this out on a Windows machine other than creating a Hackintosh, which is something I'm not going to suggest and probably won't work in your situation anyway.

Brandon said...

Jeff, killer tutorials. I started and currently write for and will definitely be referring my visitors to your blog.

Thank you for your hard work on this, you present all of the material very clearly and make it so simple to understand something as complex as 3D.

rectalogic said...

Great series of posts.

But glMaterial only accepts GL_FRONT_AND_BACK in ES (GL_FRONT and GL_BACK are not allowed)

Jeff LaMarche said...


Dude, you rock! I was wondering why GL_FRONT wouldn't work and sort of side-stepped the question in the article. Thanks so much for pointing that out, I'll go correct the article right now.


Xavier said...

Hi Jeff !
Many thanks for your tutorials, I found it a great value and a very quick & good introduction to opengl ES !
It seems that your OpenGl Empty application project doesn't work anymore on iPhone OS 3.0. Have you tryed it ? I obtain only a all white screen... It works if you target a 2.2.1 device though...

jw said...

Regarding 3.0, I've found that if you comment out the following line in method applicationDidFinishLaunching

window = [[UIWindow alloc] initWithFrame:rect];

Then it works OK (although to be honest, I don't understand why!)

Dylan said...

These posts are great! How is it possible to draw something over the sphere? I would like to draw pentagons on the sphere to make it look like a soccer ball.

Dick Applebaum said...


Great work!

With this series of blogs, you've got a pretty good start on an OpenGL ES iPhone book.

I especially want to compliment you on your writing style -- it's almost like you're sitting beside me (get your feet off the table :). You seem to have an instinct for when to break the technical intensity, and bring a smile to the reader's face (and brain).

Keep it up! I bought this 3DS soccer ball that I'd like to put into the iPhone for the grand kids...


thrashmetal101 said...

I haven't really looked into why this is, but on 3.0 (simulator and device) the sphere appears as a flat rotating round object, and not the sphere that it appears as in the 2.0 simulator. Weird.

Federico said...

To Xavier and jw...

maybe you have already come to this conclusion yourselves, but just thought i'd share my findings...

the project uses a mainwindow.xib file which declares the delegate and its own instance of UIWindow. so there's actually two windows being created and on iphone os 3.0 apparently the one in the xib file appears on top of the one defined in code.

by removing this line

window = [[UIWindow alloc] initWithFrame:rect];

only one window instance is left, so we definitely add the view to the window that will be shown.

alternatively you can

1. remove the MainWindow.xib file
2. remove the MainWindow.xib reference from the Info.plist file
3. pass the name of the app delegate class in main, like this
UIApplicationMain(argc, argv, nil, @"TextureAppDelegate");

sorry i'm a bit imprecise with names of files and props, but it's 3:15am. just couldn't stop reading the tutorials till i got to the end.

Thanks, Jeff! You have done such an amazing job!

Federico said...


i have the same problem.

the sphere looks nice and 3D-ish on simulator 2.0, but on my iphone device (running os 3.0), it's flat!

if u find a solution anywhere, can u let me know? thx

Dittmer said...

thrashmetal101, Federico and Jeff:

I had the same problem with spheres in the simulator turning into flat disks on my device (wish OS 2). With a little investigation, I discovered that in the getSolidSphere function of Jeff's code, the GLfloat nsign is never initialized. I'm not sure what it's supposed to be, but I set it to 1.0 and now have beautiful spheres on my iPhone.

Thanks for these excellent tutorials, Jeff!!

wirah said...

Hi All,

I can confirm that the removal of:

window = [[UIWindow alloc] initWithFrame:rect];


Makes my pancake visible on 3.0, and the additon of:

nsign = 1.0;

after it's declaration in

Makes my pancake into a sphere again!

Thanks guys :)

Yali said...

The code does not work for OS 3.0! What should I change the code to make it work under iPhone OS 3.0?

wirah said...

Yali, there are instructions above your comment telling you exactly what to do.

Kallewoof said...

These are more helpful than I could possibly express in words!

A note though: you say the iPhone OpenGL ES doesn't have shaders, but it does. I presume the 2.0 version didn't, or such, but my 3.0 SDK does indeed have them.

ukchan said...


I think that is about OpenGL ES 2.0.
Which is supported from iPhone 3GS.
I heard that OpenGL ES 2.0 has shader feature.

Edwin said...

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

VaSH said...

Money Money Money!

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

Ayah Fathimah said...
This comment has been removed by the author.