9.6 - Fragment Shader Debugging¶
A fragment shader performs calculations to assign a color to a pixel in a rendering. For the purposes of these tutorials on lighting, a fragment shader performs the calculations of a lighting model to assign a color to a pixel.
Fragment shaders can be difficult to debug. Debugging a normal computer program is typically done in one of two ways: 1) print intermediate values to a console window, or 2) use an interactive debugger to set breakpoints and step through the code one statement at a time. Neither method works for fragment shaders running on a GPU. So how can you debug a fragment shader if it is not working correctly?
Do Your Homework!¶
The easiest way to minimize debugging is to write a correct shader program that functions correctly. This might sound silly, but if you create good logic and good equations on paper before you start coding, it can save you the hassle of debugging. Do your homework! Don’t skimp on the design phase. Don’t guess and “hope it works.” Do meticulous design, and sometimes debugging is not required.
Substitute Intermediate Values for Colors¶
The only output of a fragment shader is the color of the pixel stored in
the gl_FragColor
output shader variable. We can substitute various
values into this color variable and visualize the values of intermediate
calculations by examining the output rendering. Experiment with the following
WebGL program by reassigning the gl_FragColor
value to various values.
Note that a color is defined as three distinct values, as is a vertex and
a vector. The only difference is the permitted range for each value. A color component is
always a value between 0.0 and 1.0. So in some cases you might need to scale
or manipulate the intermediate values before putting them into the
gl_FragColor
variable.
Try these variable substitutions as you experiment with the WebGL program below. You will be changing line 72 of the fragment shader.
Experiment #1¶
gl_FragColor = vec4(vertex_normal, v_Color.a);
Make sure you click on the “Re-start” button after you make the change. You should see a color in each pixel that represents the normal vector for each pixel. The normal vector was normalized, so each component value is between -1.0 and +1.0. When this is used as a color, the negative values will be clamped to 0.0. If you see red, the vector is pointing along the X axis. If you see green, the vector is pointing along the Y axis. If you see blue, the vector is pointing along the Z axis. Make sure you move the camera so that the vertex normals are changing. If you see only black, then the vertex normals are (0.0, 0.0, 0.0) and there is some error related to retrieving or calculating the normal vector.
Experiment #2¶
gl_FragColor = vec4(abs(vertex_normal), v_Color.a);
The abs()
function will invert any negative values in the normal vector,
but you won’t know which values were negative. This output might confuse you more than
help you.
Experiment #3¶
gl_FragColor = vec4(cos_angle, cos_angle, cos_angle, v_Color.a);
This displays the cos(angle)
as an intensity value from white (1.0)
to black (0.0). This can be very helpful in debugging the angle.
Experiment #4¶
gl_FragColor = vec4(v_Vertex, v_Color.a);
This displays the 3D location of each vertex as a color. You could use this to “sanity check” your vertex transformations.
Experiment #5¶
gl_FragColor = vec4(reflection, v_Color.a);
This displays the reflection vector of each fragment (pixel) as a color. You could use this to “sanity check” your reflection vector calculations.
Experiment¶
// Fragment shader program
precision mediump int;
precision mediump float;
// Light model
uniform vec3 u_Light_position;
uniform vec3 u_Light_color;
uniform float u_Shininess;
uniform vec3 u_Ambient_color;
// Data coming from the vertex shader
varying vec3 v_Vertex;
varying vec4 v_Color;
varying vec3 v_Normal;
void main() {
vec3 to_light;
vec3 vertex_normal;
vec3 reflection;
vec3 to_camera;
float cos_angle;
vec3 diffuse_color;
vec3 specular_color;
vec3 ambient_color;
vec3 color;
// Calculate the ambient color as a percentage of the surface color
ambient_color = u_Ambient_color * vec3(v_Color);
// Calculate a vector from the fragment location to the light source
to_light = u_Light_position - v_Vertex;
to_light = normalize( to_light );
// The vertex's normal vector is being interpolated across the primitive
// which can make it un-normalized. So normalize the vertex's normal vector.
vertex_normal = normalize( v_Normal );
// Calculate the cosine of the angle between the vertex's normal vector
// and the vector going to the light.
cos_angle = dot(vertex_normal, to_light);
cos_angle = clamp(cos_angle, 0.0, 1.0);
// Scale the color of this fragment based on its angle to the light.
diffuse_color = vec3(v_Color) * cos_angle;
// Calculate the reflection vector
reflection = 2.0 * dot(vertex_normal,to_light) * vertex_normal - to_light;
// Calculate a vector from the fragment location to the camera.
// The camera is at the origin, so negating the vertex location gives the vector
to_camera = -1.0 * v_Vertex;
// Calculate the cosine of the angle between the reflection vector
// and the vector going to the camera.
reflection = normalize( reflection );
to_camera = normalize( to_camera );
cos_angle = dot(reflection, to_camera);
cos_angle = clamp(cos_angle, 0.0, 1.0);
cos_angle = pow(cos_angle, u_Shininess);
// The specular color is from the light source, not the object
if (cos_angle > 0.0) {
specular_color = u_Light_color * cos_angle;
diffuse_color = diffuse_color * (1.0 - cos_angle);
} else {
specular_color = vec3(0.0, 0.0, 0.0);
}
color = ambient_color + diffuse_color + specular_color;
gl_FragColor = vec4(color, v_Color.a);
}
Manipulate the properties of a light source and a camera.
The left canvas shows the relative location of the light source, the camera, and an object.The right canvas shows the scene from the camera's vantage point with the light source used to render the model using ambient, diffuse, and specular lighting combined.
Manipulate the camera: | Ambient light: | Light properties: |
eye (0.0, 0.0, 5.0) | ambient percentages (0.2, 0.2, 0.2) | light location (3.0, 3.0, 3.0) |
X: -5.0 +5.0 | Red : 0.0 1.0 | X: -5.0 +5.0 |
Y: -5.0 +5.0 | Green: 0.0 1.0 | Y: -5.0 +5.0 |
Z: -5.0 +5.0 | Blue : 0.0 1.0 | Z: -5.0 +5.0 |
center (0.0, 0.0, 0.0) | ambient percentage = 0.2 0.0 1.0 |
light color = (1.0, 1.0, 1.0) |
X: -5.0 +5.0 | Red: 0.0 1.0 | |
Y: -5.0 +5.0 | Green: 0.0 1.0 | |
Z: -5.0 +5.0 | Blue: 0.0 1.0 | |
shininess = 30.0 0.1 128.0 |
Summary¶
Putting intermediate calculated values into the gl_FragColor
variable
is an art more than a science. Be creative! Debugging shader programs
can be very challenging.
Glossary¶
- debugging
- The art of finding mistakes in a computer program.