Fragment Shaders
The fragment3 shader is the last programmable stage in OpenGL’s graphics pipeline. This stage is responsible for determining the color of each fragment before it is sent to the framebuffer for possible composition into the window. After the rasterizer processes a primitive, it produces a list of fragments that need to be colored and passes this list to the fragment shader. Here, an explosion in the amount of work in the pipeline occurs, as each triangle could produce hundreds, thousands, or even millions of fragments.
Listing 2.4 in Chapter 2 contains the source code of our first fragment shader. It’s an extremely simple shader that declares a single output and then assigns a fixed value to it. In a real-world application, the fragment shader would normally be substantially more complex and be responsible for performing calculations related to lighting, applying materials, and even determining the depth of the fragment. Available as input to the fragment shader are several built-in variables such as gl_FragCoord, which contains the position of the fragment within the window. It is possible to use these variables to produce a unique color for each fragment.
Listing 3.10 provides a shader that derives its output color from gl_FragCoord. Figure 3.4 shows the output of running our original single-triangle program with this shader installed.
Figure 3.4: Result of Listing 3.10
Listing 3.10: Deriving a fragment’s color from its position
#version 450 core out vec4 color; void main(void) { color = vec4(sin(gl_FragCoord.x * 0.25) * 0.5 + 0.5, cos(gl_FragCoord.y * 0.25) * 0.5 + 0.5, sin(gl_FragCoord.x * 0.15) * cos(gl_FragCoord.y * 0.15), 1.0); }
As you can see, the color of each pixel in Figure 3.4 is now a function of its position and a simple screen-aligned pattern has been produced. The shader of Listing 3.10 created the checkered patterns in the output.
The gl_FragCoord variable is one of the built-in variables available to the fragment shader. However, just as with other shader stages, we can define our own inputs to the fragment shader, which will be filled in based on the outputs of whichever stage is last before rasterization. For example, if we have a simple program with only a vertex shader and fragment shader in it, we can pass data from the fragment shader to the vertex shader.
The inputs to the fragment shader are somewhat unlike inputs to other shader stages, in that OpenGL interpolates their values across the primitive that’s being rendered. To demonstrate, we take the vertex shader of Listing 3.3 and modify it to assign a different, fixed color for each vertex, as shown in Listing 3.11.
Listing 3.11: Vertex shader with an output
#version 450 core // 'vs_color' is an output that will be sent to the next shader stage out vec4 vs_color; void main(void) { const vec4 vertices[3] = vec4[3](vec4(0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4(0.25, 0.25, 0.5, 1.0)); const vec4 colors[] = vec4[3](vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 1.0, 0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0)); // Add 'offset' to our hard-coded vertex position gl_Position = vertices[gl_VertexID] + offset; // Output a fixed value for vs_color vs_color = color[gl_VertexID]; }
As you can see, in Listing 3.11 we added a second constant array that contains colors and index into it using gl_VertexID, writing its content to the vs_color output. In Listing 3.12 we modify our simple fragment shader to include the corresponding input and write its value to the output.
Listing 3.12: Deriving a fragment’s color from its position
#version 450 core // 'vs_color' is the color produced by the vertex shader in vec4 vs_color; out vec4 color; void main(void) { color = vs_color; }
The result of using this new pair of shaders is shown in Figure 3.5. As you can see, the color changes smoothly across the triangle.
Figure 3.5: Result of Listing 3.12