4. Adding Light to ModelViewerWe’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::Initializevoid 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); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertices[0]), &vertices[0], GL_STATIC_DRAW); // Create a new VBO for the indices if needed. ... }
// Set up various GL state. glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST);
// 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:
GL_SHININESS 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.
GL_AMBIENT 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.
GL_SPECULAR This specifies the specular color of
the surface and also requires four floats, although alpha is
ignored.
GL_EMISSION 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.
GL_DIFFUSE This specifies the diffuse color of the
surface and requires four floats. The final alpha value of the
pixel originates from the diffuse color.
GL_AMBIENT_AND_DIFFUSE 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.
Note: 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::Rendervoid RenderingEngine::Render(const vector<Visual>& visuals) const { glClearColor(0.5f, 0.5f, 0.5f, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 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. glMatrixMode(GL_MODELVIEW); glLoadIdentity(); 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; glLoadMatrixf(modelview.Pointer()); // Set the projection transform. float h = 4.0f * size.y / size.x; mat4 projection = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10); glMatrixMode(GL_PROJECTION); glLoadMatrixf(projection.Pointer());
// 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
GLView.mm.
5. Using Light PropertiesExample 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:
GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR 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.
GL_POSITION 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.
GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF 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 .
GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION,
GL_QUADRATIC_ATTENUATION 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: glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
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.
Note: 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.
|