Shaders and the Rendering Pipeline

When it comes to OpenGL, we aren't merely writing instructions for the CPU to process. We are making use of our GPU to render graphics to the screen. In order for this to happen, we must make use of the rendering pipeline to make things as fast as possible. Shaders are a part of the rendering pipeline that we can make changes to.

The rendering pipeline is a series of stages that take place in order to render an image to the screen.
Four of these stages are programmable via shaders.

  1. Vertex Shader
  2. Fragment Shader
  3. Geometry Shader
  4. Tessellation Shader
  5. Compute Shader (this is a pretty new shader. Technically a 5th shader.)

Shaders are pieces of code written in GLSL (OpenGL Shading Language), or HLSL (High-Level Shading Language) if you're using Direct3D. GLSL is based on C. GLSL and HLSL are rather similar.

The Rendering Pipeline Stages

There are 9 parts but some people may split the stages into more or less categories. This following list will do:

  1. Vertex Specification
  2. Vertex Shader (programmable)
  3. Tessellation (programmable)
  4. Geometry Shader (programmable)
  5. Vertex Post-Processing
    1. This is the end of all the vertex operations
  6. Primitive Assembly
    1. Handles groups of vertices
  7. Rasterization
    1. The conversion to fragments
  8. Fragment Shader (programmable)
  9. Per-Sample Operations
    1. Operations performed on the fragments before being rendered to the screen

Now that we have an overview of all of the stages, let us delve into each one in particular.

Vertex Specification

Vertex Specification: Creating VAO/VBO

VAOs and VBOs are quite important but how do we create them? Well, we follow the following list:

  1. Generate a VAO ID
    1. Despite dedicating some memory to the VAO on the GPU, we can't directly access that data. As such, we need an ID that we use to query the graphics card with.
  2. Bind the VAO with that ID
    1. Now that ID will be associated with that VAO
  3. Generate a VBO ID
  4. Bind the VBO with that ID (now you're working on the chosen VBO attached to the chosen VAO).
    1. OpenGL will assume that the previously bounded VAO corresponds with this newly bounded VBO.
  5. Attach the vertex data to that VBO.
  6. Define the Attribute Pointer formatting.
    1. How is the data in the VBO formatted? Is it groups of 3 or is it all spaced apart? Is it made of floats or integers?
  7. Enable the Attribute Pointer
  8. Unbind the VAO and VBO, ready for the next object to be bound
    1. Perhaps we just got done making a cube and now we want to move on to a pyramid shaped primitive.

Vertex Specification: Initiating Draw

  1. Activate Shader Program you want to use.
  2. Bind VAO of object you want to draw.
  3. Call glDrawArrays, which initiates the rest of the pipeline.

Hurray! Now we are beginning the rendering pipeline! Next, we deal with shaders.

Vertex Shader

At this point, the pipeline is in operation! We do the following:

  1. Handles vertices individually.
    1. We can handle vertex data and do things such as apply a model matrix, view matrix, offset the vertices some, etc.
  2. NOT optional.
    1. Unlike the other shaders, we must manually define the vertex shader even if it is incredibly basic.
  3. Must store something in gl_Position as it is used by later stages.
    1. Whatever goes into gl_Position is assumed to the be final position of that vertex.
  4. Can specify additional outputs that can be picked up and used by user-defined shaders later in the pipeline.
    1. For example, we could specify something for color and make use of it in our fragment shader.
  5. Inputs consist of the vertex data itself.
    1. This includes the positions of the vertices themself, texture coordinates, etc.

The following is an example of a very simple Vertex Shader written in GLSL.

#version 330 // define the version of GLSL that we are using

// layout and location are a bit new
// by setting location to 0, we won't have to query it in the
// application code. Without layout and location, we would have
// to constantly query the position.
// With location set to 0, we can easily bind the data to position 0
// in the application code.
//
// 'in' is a keyword that means input.
// 'vec3' is a vector 3 consisting of three values: x, y, and z
// 'pos' is just the name of the variable we created
layout (location = 0) in vec3 pos;

void main()
{
	// gl_Position actually requires 4 values thus we use a vec4.
	gl_Position = vec4(pos, 1.0);
}

Tessellation

Geometry Shader

This shader isn't usually taught in a lot of tutorials online but can have some uses.

Vertex Post-Processing

Next up, we have these two stages:

Primitive Assembly

Rasterization

Fragment Shader

The GLSL code for a basic Fragment Shader looks like this:

#version 330

// We could name this anything we want. Usually a
// fragment shader has a single output. The code
// will assume that this is the output we want
// to give for the color regardless if we named
// the variable color or even 'foobarmegasegayeet'
out vec4 color;

void main()
{
	// output is in terms of RGBA
	color = vec4(1.0, 0.0, 0.0, 1.0);
}

Something special here is that if we did output anything from the Vertex Shader then we can make use of it here with the 'in' keyword!

Per-Sample Operations

At this point, we go through the pipeline again and again until we terminate the program.

On the Origin of Shaders...

Creating a Shader Program

To create a Shader Program, we follow this pattern:

  1. Create empty program.
  2. Create empty shaders.
  3. Attach shader source code to shaders.
  4. Compile shaders.
  5. Attach shaders to program.
  6. Link program (creates executables from shaders and links them together).
    1. Kind of like a .exe but not quite.
  7. Validate program (optional but highly advised because debugging shaders is a pain).
    1. This helps us to make sure there are no bugs or syntactical errors
    2. Since shaders run on the graphics card itself, we can't set breakpoints or anything to debug with. Instead, we have to rely on the error messages that we get back.

Using a Shader Program

Summary

Further Reading: Check out the OpenGL wiki (perhaps called the Kronos Wiki) for a much more detailed explanation of all of these stages.