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? 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 1.
Example 1. Simple.vert
attribute vec4 Position; attribute vec4 SourceColor; varying vec4 DestinationColor; uniform mat4 Projection; uniform mat4 Modelview;
void main(void) { DestinationColor = SourceColor; gl_Position = Projection * Modelview * Position; }
|
The keywords attribute,
uniform, and varying are storage
qualifiers in GLSL. Table 1 summarizes the five
storage qualifiers available in GLSL.
Table 1. GLSL storage qualifiers
Qualifier | Maximum permitted | Readable from VS | Writable from VS | Readable from FS | Writable from FS |
---|
default | N/A | Yes | Yes | Yes | Yes |
const | N/A | Yes | Yes | Yes | Yes |
attribute | 8 vec4 | Yes | No | No | No |
uniform | 512 scalars (VS), 64 scalars
(FS) | Yes | No | Yes | No |
varying | 8 vec4 | No | Yes | Yes | No |
Figure 1 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 2.
Example 2. Boring fragment shader
varying lowp vec4 DestinationColor;
void main(void) { gl_FragColor = DestinationColor; }
|
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 2 shows specific details for the graphics
processor in the iPhone 3GS.
Note:
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 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 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[0]
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.
gl_FragCoord
This is an input variable that contains
window coordinates for the current fragment, which is useful for
image processing.
gl_FrontFacing
Use this boolean input variable to
implement two-sided lighting. Front faces are true; back faces are
false.
gl_PointCoord
This is an input texture coordinate
that’s used only for point sprite rendering.