At this point in this book, you may have written a couple simple OpenGL demos to impress your co-workers and family members. But, your app may need that extra little something to stand out from the crowd. This chapter goes over a small selection of more advanced techniques that can give your app an extra oomph.
The selection of effects dealt with in this chapter is by no means comprehensive. I encourage you to check out other graphics books, blogs, and academic papers to learn additional ways of dazzling your users. For example, this book does not cover rendering shadows; there are too many techniques for rendering shadows (or crude approximations thereof), so I can’t cover them while keeping this book concise. But, there’s plenty of information out there, and now that you know the fundamentals, it won’t be difficult to digest it.
This chapter starts off by detailing some of the more obscure texturing functionality in OpenGL ES 1.1. In a way, some of these features—specifically texture combiners, which allow textures to be combined in a variety of ways—are powerful enough to serve as a substitute for very simple fragment shaders.
The chapter goes on to cover normal maps and DOT3 lighting, useful for increasing the amount of perceived detail in your 3D models. (DOT3 simply refers to a three-component dot product; despite appearances, it’s not an acronym.) Next we discuss a technique for creating reflective surfaces that employs a special cube map texture, supported only in ES 2.0. We’ll then briefly cover anisotropic texturing, which improves texturing quality in some cases. Finally, we’ll go over an image-processing technique that adds a soft glow to the scene called bloom. The bloom effect may remind you of a camera technique used in cheesy 1980s soap operas, and I claim no responsibility if it compels you to marry your nephew in order to secure financial assistance for your father’s ex-lover.
Multitexturing was briefly introduced in the previous chapter (Image Composition and a Taste of Multitexturing), but there’s a lot more to explain. See Figure 8-1 for a high-level overview of the iPhone’s texturing capabilities.
This section doesn’t have much in the way of example code; if you’re not interested in the details of texture combination under OpenGL ES 1.1, skip to the next section (Bump Mapping and DOT3 Lighting).
Here are a few disclaimers regarding Figure 8-1. First, the diagram assumes that both texture stages are enabled; if stage 1 is disabled, the “previous color” gets passed on to become the “final color.” Second, the diagram shows only two texture stages. This is accurate for first- and second-generation devices, but newer devices have eight texture units.
In Figure 8-1, the
“primary” color comes from the interpolation of per-vertex colors.
Per-vertex colors are produced by lighting or set directly from the
Each of the two texture environments is configured to combine its various inputs and produce an output color. The default configuration is modulation, which was briefly mentioned in Chapter 5; this means that the output color results from a per-component multiply of the previous color with the lookup color.
There are a whole slew of ways to configure
each texture environment using the
glTexEnv function. In my opinion, this is
the worst function in OpenGL, and I’m thankful that it doesn’t exist in
OpenGL ES 2.0. The expressiveness afforded by GLSL makes
void glTexEnvi(GLenum target, GLenum pname, GLint param); void glTexEnviv(GLenum target, GLenum pname, const GLint* params); void glTexEnvf(GLenum target, GLenum pname, GLfloat param); void glTexEnvfv(GLenum target, GLenum pname, const GLfloat* params);
There are actually a couple more variants for fixed-point math, but I’ve omitted them since there’s never any reason to use fixed-point math on the iPhone. Because of its chip architecture, fixed-point numbers require more processing than floats.
The first parameter,
is always set to
GL_TEXTURE_ENV, unless you’re enabling
point sprites as described in Rendering Confetti, Fireworks, and More: Point Sprites. The second
pname, can be any of the
OutputColor = LookupColor
OutputColor = LookupColor * PreviousColor
OutputColor = PreviousColor * (1 - LookupAlpha) + LookupColor * LookupAlpha
OutputColor = PreviousColor * (1 - LookupColor) + LookupColor * ConstantColor
OutputColor = PreviousColor + LookupColor
glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, myFirstTextureObject); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, mySecondTextureObject); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
If the mode is set to
GL_COMBINE, you can set up two types of combiners:
the RGB combiner and the alpha
combiner. The former sets up the output color’s RGB
components; the latter configures its alpha value.
Each of the two combiners needs to be set up
using at least five additional (!) calls to
One call chooses the arithmetic operation (addition, subtraction, and so
on), while the other four calls set up the arguments to the operation.
For example, here’s how you can set up the RGB combiner of texture stage
glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, myTextureObject); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // Tell OpenGL which arithmetic operation to use: glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, <operation>); // Set the first argument: glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, <source0>); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, <operand0>); // Set the second argument: glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, <source1>); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, <operand1>);
// Tell OpenGL which arithmetic operation to use: glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, <operation>); // Set the first argument: glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, <source0>); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, <operand0>); // Set the second argument: glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, <source1>); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, <operand1>);
OutputColor = Arg0
OutputColor = Arg0 * Arg1
OutputColor = Arg0 + Arg1
OutputColor = Arg0 + Arg1 - 0.5
OutputColor = Arg0 ∗ Arg2 + Arg1 ∗ (1 − Arg2)
OutputColor = Arg0 - Arg1
OutputColor = 4 * Dot(Arg0 - H, Arg1 - H) where H = (0.5, 0.5, 0.5)
OutputColor = 4 * Dot(Arg0 - H, Arg1 - H) where H = (0.5, 0.5, 0.5)
GL_DOT3_RGBA produce a scalar rather than a vector.
GL_DOT3_RGB, that scalar is duplicated into each
of the three RGB channels of the output color, leaving alpha untouched.
GL_DOT3_RGBA, the resulting scalar is written
out to all four color components. The dot product combiners may seem
rather strange, but you’ll see how they come in handy in the next
All the different ways of calling
glTexEnv are a bit confusing, so I think it’s best to
go over a specific example in detail; we’ll do just that in the next
section, with an in-depth explanation of the DOT3 texture combiner and
its equivalent in GLSL.