Lesson 1: Introduction

Before we start programming our own shader, let's first give a small introduction into the topic. What exactly is a Shader? Where and how are they used? How do they fit into the pipeline of OpenGL? All these questions will be explained in this first lesson.


 1. The OpenGL Pipeline

To be able to explain what vertex and pixel shader really are, we need to have a closer look at the rendering pipeline of OpenGL. The part we are going to investigate uses the fixed functionality of OpenGL. This means that the evaluations made in this stage are transparent for the user and can't be manipulated by him. It's part of the graphic driver's implementation.


Figure #1: Exemplary part of the OpenGL pipeline

 Vertex Transformation

In this context a vertex contains a lot of different attributes, like its position in space and its color, normals, and texture coordinates. These are used as input values to this stage. Operations performed in this stage may be:


 Primitive Assembly and Rasterization

The input values to this stage are the transformed vertices, with their connectivity information (how the vertices are connected to each other). Using this information, the geometric primitives are assembled here.

The rasterization step determines the fragments and pixel positions of a primitive. In this stage a fragment coresponds approximately to the final pixel on the screen, but they don't just contain a single color value, but also attributes like normals and texture coordinates. The final color of the screen pixel is determined later, when semi-transparent fragments, which may overlap with this fragment are also taken into account. So, just try to see fragments as an advanced pre-stage of a final pixel on the screen. Many overlaping fragments may be taken into account, when calculating the final color of a pixel.

The outcomes of this stage are two-fold:

Combining the outcomes of the vertex transformation stage with the connectivity information of the other vertices that make up the primitive the actual fragment is in allows this stage to calculate exact, interpolated values for the fragment's attributes. For example, every fragment has its own transformed position, which was calculated by gathering the positions of the vertices that make up the current fragment's primitive and interpolating between them. The same is done with the color value of a fragment that, for instance belongs to a triangle with three different colors at each vertex.


 Fragment Texturing and Coloring

In the previous stage, a fragment's color was computed using interpolation, which now can be combined with a texel from a texture. Afterwards, fog information is also applied to the fragment and, finally, the effective color and depth value of this fragment is handed over to the next stage. Keep in mind that different overlapping primitives may produce fragments with the same raster position, which will then be combined to a single screen pixel in the next stage.


 Raster Operations

Input values for this stage are:

At first, test operations are performed, like:

If all of these succeed, the pixel is written into the framebuffer, taking in account the current blending mode. Because the fragment texturing and coloring stage doesn't have access to the framebuffer, this is the only point where blending is performed inside the pipeline.


 Summary

The following picture summarizes all the stages mentioned above:


Figure #2: Summary of the fixed functionality



 2. Vertex- And Pixel Shaders

Nowadays, modern graphic boards are offering developers the possibility to define the functionality of two of the above mentioned stages themselves:


 The Vertex Processor

The vertex processor is responsible for executing the vertex shaders. Input values are, like in the vertex transformation stage, the vertex's position in space, its color, normals etc. Therefore, you can write your own vertex shader code for tasks such as:

A vertex shader's output is the projected position of a vertex, which is written into the variable gl_Position. Except for writing this variable (normally performed by transforming the position by the modelview and projection matrix) the developer isn't forced to do any of the steps mentioned above. However, then it won't be done anywhere, because if a vertex shader is written, the fixed functionality of the vertex transformation is replaced by it! As a consequence, you can't just implement the lighting and let the fixed functionality take care of the computation of vertex's texture coordinates. It's either the shader or the fixed functionality.

As mentioned before, the vertex processor has no clue about connectivity information. Thus, primitive based operations, like backface culling can't be implemented using a vertex shader. Every vertex is processed seperately from the others. However, it's possible to access OpenGL states. Direct access to the framebuffer is not yet suppported by current hardware.


 The Fragment Processor

The fragment processor takes care of the pixel shaders. Possible tasks are, for instance:

The input values of this unit consist of the interpolated values of the previous stage in the pipeline, like vertex position, color, normals, etc. Also, the developer is given the possibility to hand over values from the vertex shader. These are interpolated automatically by the hardware, based on vertex connectivity. While the vertex shader focuses on the vertices themselves, the pixel shader deals with the fragments inside the primitive those vertices define.

Like the vertex shader, the pixel shader replaces the entire fixed functionality of its underlying pipeline stage. Therefore, texturizing a fragment by yourself, but calculating its fog value using the fixed pipeline is not possible.

Also, like the vertex shader, the pixel shader only processes a single fragment at a time. It has access to the OpenGL states too, but it's not aware of its adjacent fragments.

Furthermore, a pixel shader can't alter a fragment's position. The relevant matrix multiplications have already been made previously. The pixel shader has access to the fragment's position, but it can't change it.

In contrast to the vertex shader, the pixel shader has two options:

In addition, it is possible to overwrite the depth value of the fragment that was computed by the previous stage.

Like for vertex shaders, it's not possible for a pixel shader to access the framebuffer. Even though, it is possible to access the texture memory, framebuffer operations, like blending, are performed by the pipeline stages after the pixel shader.