O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Shaders Demystified

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

attribute vec4 Position; 1
attribute vec4 SourceColor; 
varying vec4 DestinationColor; 2
uniform mat4 Projection; 3
uniform mat4 Modelview; 

void main(void) 4
    DestinationColor = SourceColor; 5
    gl_Position = Projection * Modelview * Position; 6

Declare two 4D floating-point vectors with the attribute storage qualifier. Vertex shaders have read-only access to attributes.


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.


Declare two 4×4 matrices as uniforms. Much like attributes, the vertex shader has read-only access to uniforms. But unlike vertex attributes, uniforms cannot change from one vertex to the next.


The entry point to the shader. Some shading languages let you define your own entry point, but with GLSL, it’s always main.


No lighting or fancy math here; just pass the SourceColor attribute into the DestinationColor varying.


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.

The keywords attribute, uniform, and varying are storage qualifiers in GLSL. Table 4-1 summarizes the five storage qualifiers available in GLSL.

Table 4-1. GLSL storage qualifiers

QualifierMaximum permitted[a]Readable from VSWritable from VSReadable from FSWritable from FS
attribute8 vec4YesNoNoNo
uniform512 scalars (VS), 64 scalars (FS)YesNoYesNo
varying8 vec4NoYesYesNo

[a] Implementation-specific data; pertains only to the iPhone 3GS.

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.

Shader-centric view of OpenGL (VS = vertex shader, FS = fragment shader)

Figure 4-12. Shader-centric view of OpenGL (VS = vertex shader, FS = fragment shader)

The fragment shader we’ve been using so far is incredibly boring, as shown in Example 4-13.

Example 4-13. Boring fragment shader

varying lowp vec4 DestinationColor;1

void main(void)2
    gl_FragColor = DestinationColor;3

This declares a 4D floating-point varying (read-only) with lowp precision. Precision qualifiers are required for floating-point types in fragment shaders.


The entry point to every fragment shader is its main function.


gl_FragColor is a special built-in vec4 that indicates the output of the 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 lowp, mediump, and 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 precision highp float; to the top of your shader.

Table 4-2. Floating-point precision in third-generation devices

QualifierUnderlying typeRangeTypical usage
highp32-bit floating point[−9.999999×1096,+9.999999×1096]colors, normals
mediump16-bit floating point[–65520, +65520]texture coordinates
lowp10-bit fixed point[–2, +2]vertex positions, matrices

Also of interest in Example 4-13 is the gl_FragColor 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:


gl_FragData is an array of output colors that has only one element. This exists in OpenGL ES only for compatibility reasons; use gl_FragColor instead.


This is an input variable that contains window coordinates for the current fragment, which is useful for image processing.


Use this boolean input variable to implement two-sided lighting. Front faces are true; back faces are false.


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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required