Before we add lighting to the ES 2.0 rendering engine of ModelViewer, let’s go over some shader fundamentals. What exactly is a shader? In Chapter 1, we mentioned that shaders are relatively small snippets of code that run on the graphics processor and that thousands of shader instances can execute simultaneously.
Let’s dissect the simple vertex shader that we’ve been using in our sample code so far, repeated here in Example 4-12.
Example 4-12. Simple.vert
Declare a 4D floating-point vector as a varying. The vertex shader must write to all varyings that it declares—if it doesn’t, OpenGL’s shader compiler will report an error. The initial value is undefined.
Here we transform the position by the
projection and model-view matrices, much like OpenGL ES 1.1
automatically does on our behalf. Note the usage of
gl_Position, which is a special output variable
built into the vertex shading language.
varying are storage
qualifiers in GLSL. Table 4-1 summarizes the five
storage qualifiers available in GLSL.
Table 4-1. GLSL storage qualifiers
Figure 4-12 shows one way to visualize the flow of shader data. Be aware that this diagram is very simplified; for example, it does not include blocks for texture memory or program storage.
The fragment shader we’ve been using so far is incredibly boring, as shown in Example 4-13.
Example 4-13. Boring fragment shader
Perhaps the most interesting new concept here
is the precision qualifier. Fragment shaders require a precision qualifier
for all floating-point declarations. The valid qualifiers are
highp. The GLSL
specification gives implementations some leeway in the underlying binary
format that corresponds to each of these qualifiers; Table 4-2 shows specific details for the graphics
processor in the iPhone 3GS.
An alternative to specifying precision in
front of every type is to supply a default using the
precision keyword. Vertex shaders implicitly have a
default floating-point precision of
highp. To create
a similar default in your fragment shader, add
float; to the top of your shader.
Table 4-2. Floating-point precision in third-generation devices
|Qualifier||Underlying type||Range||Typical usage|
|highp||32-bit floating point||[−9.999999×1096,+9.999999×1096]||colors, normals|
|mediump||16-bit floating point||[–65520, +65520]||texture coordinates|
|lowp||10-bit fixed point||[–2, +2]||vertex positions, matrices|
Also of interest in Example 4-13 is the
variable, which is a bit of a special case. It’s a variable that is built
into the language itself and always refers to the color that gets applied
to the framebuffer. The fragment shading language also defines the
following built-in variables:
This is an input texture coordinate that’s used only for point sprite rendering; we’ll cover it in the section Rendering Confetti, Fireworks, and More: Point Sprites.