iPhone 3D Programming : Adding Depth and Realism - Lighting Up (part 2)

1/9/2011 4:33:07 PM

4. Adding Light to ModelViewer

We’ll first add lighting to the OpenGL ES 1.1 backend since it’s much less involved than the 2.0 variant. Example 1 shows the new Initialize method (unchanged portions are replaced with ellipses for brevity).

Example 1. ES1::RenderingEngine::Initialize
void RenderingEngine::Initialize(const vector<ISurface*>& surfaces)
vector<ISurface*>::const_iterator surface;
for (surface = surfaces.begin(); surface != surfaces.end(); ++surface) {

// Create the VBO for the vertices.
vector<float> vertices;
(*surface)->GenerateVertices(vertices, VertexFlagsNormals);
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
vertices.size() * sizeof(vertices[0]),

// Create a new VBO for the indices if needed.

// Set up various GL state.

// Set up the material properties.
vec4 specular(0.5f, 0.5f, 0.5f, 1);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular.Pointer());
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);

m_translation = mat4::Translate(0, 0, -7);

Example 1 uses some new OpenGL functions: glMaterialf and glMaterialfv. These are useful only when lighting is turned on, and they are unique to ES 1.1—with 2.0 you’d use glVertexAttrib instead. The declarations for these functions are the following:

void glMaterialf(GLenum face, GLenum pname, GLfloat param);
void glMaterialfv(GLenum face, GLenum pname, const GLfloat *params);

The face parameter is a bit of a carryover from desktop OpenGL, which allows the back and front sides of a surface to have different material properties. For OpenGL ES, this parameter must always be set to GL_FRONT_AND_BACK.

The pname parameter can be one of the following:


This specifies the specular exponent as a float between 0 and 128. This is the only parameter that you set with glMaterialf; all other parameters require glMaterialfv because they have four floats each.


This specifies the ambient color of the surface and requires four floats (red, green, blue, alpha). The alpha value is ignored, but I always set it to one just to be safe.


This specifies the specular color of the surface and also requires four floats, although alpha is ignored.


This specifies the emission color of the surface. We haven’t covered emission because it’s so rarely used. It’s similar to ambient except that it’s unaffected by light sources. This can be useful for debugging; if you want to verify that a surface of interest is visible, set its emission color to white. Like ambient and specular, it requires four floats and alpha is ignored.


This specifies the diffuse color of the surface and requires four floats. The final alpha value of the pixel originates from the diffuse color.


Using only one function call, this allows you to specify the same color for both ambient and diffuse.

When lighting is enabled, the final color of the surface is determined at run time, so OpenGL ignores the color attribute that you set with glColor4f or GL_COLOR_ARRAY. Since you’d specify the color attribute only when lighting is turned off, it’s often referred to as nonlit color.


As an alternative to calling glMaterialfv, you can embed diffuse and ambient colors into the vertex buffer itself, through a mechanism called color material. When enabled, this redirects the nonlit color attribute into the GL_AMBIENT and GL_DIFFUSE material parameters. You can enable it by calling glEnable(GL_COLOR_MATERIAL).

Next we’ll flesh out the Render method so that it uses normals, as shown in Example 2. New/changed lines are in bold. Note that we moved up the call to glMatrixMode; this is explained further in the callouts that follow the listing.

Example 2. ES1::RenderingEngine::Render
void RenderingEngine::Render(const vector<Visual>& visuals) const
glClearColor(0.5f, 0.5f, 0.5f, 1);

vector<Visual>::const_iterator visual = visuals.begin();
for (int visualIndex = 0;
visual != visuals.end();
++visual, ++visualIndex)

// Set the viewport transform.
ivec2 size = visual->ViewportSize;
ivec2 lowerLeft = visual->LowerLeft;
glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);

// Set the light position.
vec4 lightPosition(0.25, 0.25, 1, 0);
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition.Pointer());

// Set the model-view transform.
mat4 rotation = visual->Orientation.ToMatrix();
mat4 modelview = rotation * m_translation;

// Set the projection transform.
float h = 4.0f * size.y / size.x;
mat4 projection = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);

// Set the diffuse color.
vec3 color = visual->Color * 0.75f;
vec4 diffuse(color.x, color.y, color.z, 1);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.Pointer());

// Draw the surface.
int stride = 2 * sizeof(vec3);
const Drawable& drawable = m_drawables[visualIndex];
glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
glVertexPointer(3, GL_FLOAT, stride, 0);
const GLvoid* normalOffset = (const GLvoid*) sizeof(vec3);
glNormalPointer(GL_FLOAT, stride, normalOffset);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
glDrawElements(GL_TRIANGLES, drawable.IndexCount, GL_UNSIGNED_SHORT, 0);

That’s it! Figure 4 depicts the app now that lighting has been added. Since we haven’t implemented the ES 2.0 renderer yet, you’ll need to enable the ForceES1 constant at the top of

Figure 4. ModelViewer with lighting

5. Using Light Properties

Example 2 introduced a new OpenGL function for modifying light parameters, glLightfv:

void glLightfv(GLenum light, GLenum pname, const GLfloat *params);

The light parameter identifies the light source. Although we’re using only one light source in ModelViewer, up to eight are allowed (GL_LIGHT0–GL_LIGHT7).

The pname argument specifies the light property to modify. OpenGL ES 1.1 supports 10 light properties:


As you’d expect, each of these takes four floats to specify a color. Note that light colors alone do not determine the hue of the surface; they get multiplied with the surface colors specified by glMaterialfv.


The position of the light is specified with four floats. If you don’t set the light’s position, it defaults to (0, 0, 1, 0). The W component should be 0 or 1, where 0 indicates an infinitely distant light. Such light sources are just as bright as normal light sources, but their “rays” are parallel. This is computationally cheaper because OpenGL does not bother recomputing the L vector (see Figure 4-9) at every vertex.


You can restrict a light’s area of influence to a cone using these parameters. Don’t set these parameters if you don’t need them; doing so can degrade performance. I won’t go into detail about spotlights since they are somewhat esoteric to ES 1.1, and you can easily write a shader in ES 2.0 to achieve a similar effect. Consult an OpenGL reference to see how to use spotlights .


These parameters allow you to dim the light intensity according to its distance from the object. Much like spotlights, attenuation is surely covered in your favorite OpenGL reference book. Again, be aware that setting these parameters could impact your frame rate.

You may’ve noticed that the inside of the cone appears especially dark. This is because the normal vector is facing away from the light source. On third-generation iPhones and iPod touches, you can enable a feature called two-sided lighting, which inverts the normals on back-facing triangles, allowing them to be lit. It’s enabled like this:


Use this function with caution, because it is not supported on older iPhones. One way to avoid two-sided lighting is to redraw the geometry at a slight offset using flipped normals. This effectively makes your one-sided surface into a two-sided surface. For example, in the case of our cone shape, we could draw another equally sized cone that’s just barely “inside” the original cone.


Just like every other lighting function, glLightModelf doesn’t exist under ES 2.0. With ES 2.0, you can achieve two-sided lighting by using a special shader variable called gl_FrontFacing.

  •  iPhone 3D Programming : Adding Depth and Realism - Surface Normals (part 2)
  •  iPhone 3D Programming : Adding Depth and Realism - Surface Normals (part 1)
  •  iPhone 3D Programming : Adding Depth and Realism - Filling the Wireframe with Triangles
  •  iPhone 3D Programming : Adding Depth and Realism - Creating and Using the Depth Buffer
  •  iPhone 3D Programming : Adding Depth and Realism - Examining the Depth Buffer
  •  iPhone 3D Programming : HelloCone with Fixed Function
  •  iPhone 3D Programming : Vector Beautification with C++
  •  jQuery 1.3 : An image carousel
  •  jQuery 1.3 : Headline rotator
  •  Silverlight : Print a Document
  •  Silverlight : Capture a Webcam
  •  Silverlight : Make Your Application Run out of the Browser
  •  Silverlight : Put Content into a 3D Perspective
  •  Silverlight : Response to Timer Events on the UI Thread
  •  Silverlight : Build a Download and Playback Progress Bar
  •  Silverlight : Play a Video
  •  C# 4.0 : Add a Static Constructor and Initialization
  •  C# 4.0 : Add a Constructor
  •  .NET Compact Framework : Font Selection
  •  .NET Compact Framework : Drawing Text
    Top 10
    Most Favorite Business Softwares – Feb 2013
    Maingear Nomad 15 – Don’t Judge A Gaming Laptop By Its Cover
    MSI GX60 Gaming Notebook - Great Looks And A Fast GPU
    OCUK Limited Edition P170EM - A Great Screen And Balanced Setup
    Samsung Series 5 550P5C-S03 Review - A Perfect Farewell To Windows 7
    Kobo Mini eReader Review
    Keep Kids Online Safely (Part 3)
    Keep Kids Online Safely (Part 2)
    Keep Kids Online Safely (Part 1)
    Nikon 24-85MM F3.5-4.5G ED-IF VR With Amazing Optical Performance
    Most View
    Microsoft Windows Home Server 2011 : Modifying User Accounts
    Silverlight : Controls - Customizing a Control's Basic Appearance
    How To Build Your Own PC From Scratch (Part 2)
    Asus P8Z77-I Deluxe - The Best Mini-ITX Motherboard
    Company Profiles: Twitter
    Buyer’s Guide - Inkjet printers (Part 1) - Brother MFC-J625DW, Canon PIXMA MX360, Canon PIXMA PRO-1, Dell V313W All-In-One Printer
    Exchange Server 2010 : Utilize the Availability Options for Servers Based on Role (part 1) - Load-Balance Client Access Servers
    Windows Server 2003 : Server Clustering (part 3) - Creating a New Cluster Group, Adding a Resource to a Group
    Plantronics Voyager Legend
    Smartphones and Accessories - January 2013 (Part 1)
    Crucial M4
    New Restrictions On Old Office Software (Part 2)
    Separating BPM and SOA Processes : BPM-Oriented Disputes with TIBCO (part 1) - Architecture & iProcess Business Processes
    How Much Is Your Data Worth? (Part 1)
    Upgrading to Windows Server 2003 : Preparing Domains and Computers
    MediaElement in Silverlight
    Server-Side Browser Detection and Content Delivery : Mobile Detection (part 1) - HTTP
    Perform Other Pre-Installation Tasks
    Windows 7 : Putting Windows Explorer to Work for You
    Business Intelligence in SharePoint 2010 with Business Connectivity Services : Consuming External Content Types (part 1) - External Lists & External Data