CS 315 Lab - Toon Shading
Overview
While the Phong Reflectance Model is a good approximation for achieving a realistic appearance, often we want non-realistic shading for artistic reasons. For example, maybe we want our 3D elements to look like a cartoon (e.g., to help avoid the uncanny valley), or we want to emphasize particular aspects of our model through the shader. The process is called non-photorealistic rendering.
In this short lab, you will be working with GLSL to implement a form of non-photorealistic rendering known as "toon shading" or "cel shading"; which achieves a cartoonish or hand-drawn appearance of 3D meshes.
This lab will give you a chance to practice working with GLSL (beyond the modifications you're making for homework), as well as to explore a different kind of reflectance model!
Necessary Files
You are welcome to use the lecture demo code as a starting point for this lab; that way you'll have the mesh loading all in place.
- It's easy to test this with the teapot; make sure that is currently showing!
Alternatively, you might want to implement this shading on the scene you are rendering for Homework 5; that will give you a different set of meshes to work with. The vast majority of changes you'll be making will be in the GLSL shader, so this should not interfere with the rest of your assignment--though it's a good idea to make a backup!
Lab Details
The first thing you should do is make a new shader to do your toon shading. Since we're interested in per-pixel shading, this should be a fragment shader (e.g., toon.frag
). You can use the pass-through vertex shader from the lecture demo code.
- You might copy and paste in the different
uniform
andvarying
declarations from the lecture code'sphong.frag
shader, so that is all ready. - Remember to specify which shader to use in the
scene.js
file!
There are three components to toon shading that you'll need to implement.
-
Stylized Diffuse Reflection
The most noticeable aspect of a toon-shaded mesh is that the diffuse lighting changes are not smooth; rather, the object is colored in "bands" of varying brightness. The example teapot render at the top of the page has a light green band for the most brightly lit areas, a mid-green band for the next brightly lit, then a darker green, and then a very dark green--the teapot is shaded with four colors total. This gives the object a cartoonish appearance, but we are still able to see its depth!
- To achieve this effect you'll use a similar process to modeling diffuse reflection in the Phong model. However, instead of having the reflected color be based on the continous angles between the fragments and the light source, the reflected color will be determined by some threshold of angles. For example: an angle between 0 and 20 degrees might give yellow, an angle between 20 and 60 degrees gives the primary lighter green, and angle between 60 and 75 degrees gives a darker green, etc.
-
In your shader, calculate the angle between the light direction and your fragment location the same way we did with the Phong model (e.g., the dot product of the normalized vectors). But instead of using that angle as a weight for the reflected color, you can use a series of
if else
blocks to determine what color to reflect.- I achieved the above four-color shading using thresholds at 0.95, 0.5, and 0.25. You can adjust these as desired (e.g., if you want more or fewer bands).
- For colors to use: I started with a base green (the majority of the teapot), and then increased its intensity by about 150% for the "bright" portion, and then reduced its intensity to about 60% and 30% for the darker bands respectively. You can use whatever color proportions you want; find something that looks good for your model!
-
You can hard-code in the thresholds, though it is also good practice to pass them in as a
uniform3fv
(a uniform vector/array of 3 floats!)
Overall, this should produce a rendering that looks like the mesh was colored in by hand, like a comic book!
-
Outlines
Now you've code simple coloring; however most hand-drawn graphics tend to look like an outline was filled in--thus we want to include some rendering that will show the outline (e.g., the black line between the teapot and the spout).
- There are a number of ways to achieve this effect. One of the simplest is to use the fact that as we get to the "silhouette edge" of a smooth surface, the normal vector of the surface is moving further and further away from the vector to the eye. Right before we go to the "backside" of the surface (e.g., when we're on the edge), the angle between those vectors will be close to 90 degrees--meaning the cosine of the angle will be close to 0!
-
In your shader, use the dot product to calculate the cosine of the angle between the vector to the eye (like we used with specular reflection) and the normal to the surface. If this angle is small (e.g., less than some threshold), you should color that fragment black. This will let you effectively draw an outline of your mesh!
- Theoretically, you should be able to let the "threshold" on whether to be black be a small number, like angle < 0.1. However, the meshes I've tested with have worked best with a large threshold of angle < 0.45 (possibly due to the projection stuff...) Play around with different values to get something that looks good to you.
-
Stylized Specular Reflection
Finally, cartoons often have highly stylized specular highlights--generally large white spots to indicate that something in shiny (e.g., here and here).
-
To implement this, we'll use the same equation for calculating the specular reflection as in the Phong model (based on the angle between the camera and the reflection). However, rather than having the specular reflection fade over an area, we'll use the same idea as with the diffuse reflection and specify a threshold at which to show the reflection. If the spot is part of the highlight (falls within the threshold), color it white. If not, then don't include any specular component.
- I used a threshold of the specular coefficient being greater than 0.5, but again you should try different values to get an effect you like!
-
To implement this, we'll use the same equation for calculating the specular reflection as in the Phong model (based on the angle between the camera and the reflection). However, rather than having the specular reflection fade over an area, we'll use the same idea as with the diffuse reflection and specify a threshold at which to show the reflection. If the spot is part of the highlight (falls within the threshold), color it white. If not, then don't include any specular component.
Once you've got a toon shaded mesh you're finished with the lab! You are welcome to include this shader in your rendered scene--e.g., if you want a cartoon inside your scene.