Basic Toon Shading
Let’s load an obj model
//sketch.js
let obj;
function preload()
{
obj = loadModel('./duck.obj');
...
}
//sketch.js
function draw()
{
...
// OBJ
scale(-280);
translate(0, -0.5, 0);
rotateY(2);
// rotateX(frameCount * 0.01);
// rotateY(frameCount * 0.01);
model(obj);
}
The Toon Shading Model
As the toon shader does not differentiate much between the different light and and reflection properties, we start off with a simpler code frame.
On a Side Note: Eveything is done in the fragment shader sphere_toon.frag
TODO 1: Flat Base Color
As base or ‘ambient’ color, we flat shade with the material diffuse color
// TODO 1a
// 'Ambient' color is the material color
gl_FragColor = uMaterialColor;
// TODO 1b
all_lights(v_pos_view, v_normal, toon_shading);
gl_FragColor.rgb = toon_shading.rgb;
// TODO 1c
toon_shading = uMaterialColor.rgb;
TODO 2: Step function for diffuse shading
First we compute the diffuse component as usual
// TODO 2a
float diffuse = max(0.0, dot(-light_dir, v_normal));
//toon_shading = vec3(diffuse); // only for testing
The compute diffuse value has a range of 0..1. We can multiply this with the number of shading levels we want to create and use the floor
function to create the steps. Then we transform the value back to a range of 0..1.
// TODO 2b
const float LEVELS = 5.0;
const float SCALING = 1.0 / LEVELS;
// TODO 2c
// Diffuse step function
toon_shading += uDirectionalSpecularColors[j] * floor(diffuse * LEVELS) * SCALING;
TODO 3: Outline
For creating an outline-like as black line, we can compute and check the the angle between the view direction and the surface normal in the same way we as for the diffuse value for the angle between light direction and the surface normal.
We want to draw an outline if the view direction and the surface normal are somewhat perpendicular to each other, meaning where their dot product is around 0.
As the outline only depends on the view, we do not need to compute it for each light separately.
// TODO 3
// Outline
// Black color if dot product is smaller than 0.2
// else keep the same colors
float edge = (dot(view_dir, normal) > 0.2) ? 1.0 : 0.0;
toon_shading *= edge;
TODO 4: Specular Highlight Dot
For this we clamp the specular reflection value
// TODO 4a
// A Small Highlight
vec3 R = reflect(light_dir, normal);
float spec = pow(max(0.0, dot(R, view_dir)), uShininess);
float mask = (spec > 0.9) ? 0.8 : 0.0;
toon_shading += (spec * mask);
We can furthermore use the spec
value to clamp an outline for the highlight if so desired
// TODO 4b
// Outline for Highlight
// float edge = (spec > 0.88 && spec < 0.9) ? -1.0 : 1.0; // simply black
// toon_shading *= edge;
if (spec > 0.88 && spec < 0.9)
{
toon_shading = uMaterialColor.rgb;
}
The End 👩🏼🎨