A Simple OpenGL Shading Example
Now that we've described the OpenGL Shading Language, let's look at a simple example. In this example, we'll be applying a brick pattern to an object. The brick pattern will be calculated entirely within a fragment shader. If you'd prefer to skip ahead to the next chapter for a more in-depth discussion of the API that allows shaders to be defined and manipulated, feel free to do so.
In this example, and in most of the others in this book, there are three essential components: the source code for the vertex shader, the source code for the fragment shader, and the application code that is used to initialize and use these shaders. This chapter focuses on the vertex and fragment shaders. The application code for using these shaders will be discussed in Section 7.11, after the details of the OpenGL Shading Language API have been discussed.
With this first example, we'll take a little more time discussing the details in order to give you a better grasp of what's going on. In examples later in the book, we'll focus mostly on the details that differ from previous examples.
6.1 Brick Shader Overview
One approach to writing shaders is to come up with a description of the effect that you're trying to achieve and then decide which parts of the shader need to be implemented in the vertex shader, which need to be implemented in the fragment shader, and how the application will tie everything together.
In this example, we'll develop a shader that applies a computed brick pattern to all objects that are drawn. We're not going to attempt the most realistic looking brick shader, but rather a fairly simple one that illustrates many of the concepts we introduced in the previous chapters. We won't be using textures for this brick pattern; the pattern itself will be generated algorithmically. We can build a lot of flexibility into this shader by parameterizing the different aspects of our brick algorithm.
Let's first come up with a description of the overall effect we're after:
A single light source
Diffuse and specular reflection characteristics
A brick pattern that is based on the position in modeling coordinates of the object being renderedwhere the x coordinate will be related to the brick horizontal position and the y coordinate will be related to the brick vertical position
Alternate rows of bricks will be offset by one-half the width of a single brick
Parameters that control the brick color, mortar color, brick-to-brick horizontal distance, brick-to-brick vertical distance, brick width fraction (ratio of the width of a brick to the overall horizontal distance between two adjacent bricks), and brick height fraction (ratio of the height of a brick to the overall vertical distance between two adjacent bricks)
The brick geometry parameters that we'll be using are illustrated in Figure 6.1. Brick size and brick percentage parameters will both be stored in user-defined uniform variables of type vec2. The horizontal distance between two bricks, including the width of the mortar, will be provided by BrickSize.x. The vertical distance between two rows of bricks, including the height of the mortar, will be provided by BrickSize.y. These two values will be given in units of modeling coordinates. The fraction of BrickSize.x represented by the brick only will be provided by BrickPct.x. The fraction of BrickSize.y represented by the brick only will be provided by BrickPct.y. These two values will be in the range [0,1]. Finally, the brick color and the mortar color will be represented by the variables BrickColor and MortarColor.
Figure 6.1. Parameters for defining brick
Now that we're armed with a firm grasp of our desired outcome, we'll design our vertex shader, then our fragment shader, and then the application code that will tie it all together.