Toon Shading
Cutting continuous lighting into a few fixed brightness steps is handled by this block:
Let's break it down.
What toon step shading does
Normal lighting is a smooth gradient — brightness transitions seamlessly from light to dark. Toon shading "quantizes" that gradient into a handful of fixed brightness bands, like a print with only a few shades of gray. The result: sharp, hard-edged light boundaries that look hand-drawn or comic-book-like.
The code uses intensity = dot(normal, lightDir) and maps it to 5 brightness levels: 0.2 / 0.4 / 0.6 / 0.8 / 1.0.
How the outline works
The outline is also a view-angle trick:
At the sphere's silhouette, the normal is nearly perpendicular to the view, so dot(normal, viewDir) is near 0 and edge is near 1. smoothstep converts this into a 0–1 mask; thickness controls the line width. Then mix(color, vec3(0.0), edgeFactor * 0.8) blends that area toward black.
The animation
The light direction rotates over time:
You can watch the shadow boundary travel across the sphere and see clearly how the shading jumps between levels instead of smoothly transitioning.
Try changing it
| Change | Effect |
|---|---|
| Reduce to 3 tiers instead of 5 | Coarser shading, simpler comic look |
outlineThickness to 0.2 | Thinner outline |
edgeFactor * 0.8 to edgeFactor * 1.0 | Fully black outline |
Exercise
The exercise toonShading function already has 4 tiers (slightly different thresholds) and the outline thickness is set — the shader runs correctly as-is. Try changing the four brightness multipliers (1.0, 0.7, 0.45, 0.25) to (1.0, 0.6, 0.3, 0.1) and observe how the shadow contrast shifts.
Answer Breakdown
toonShading divides intensity = dot(normal, lightDir) into tiers:
Four bands: above 0.75 is the brightest, below 0.2 is the darkest. There is no smooth blending between levels — that hard jump is exactly what creates the cartoon look.
The outlineThickness controls line width; the sin(u_time) * 0.08 term makes the outline breathe slightly over time.
Try setting the lowest multiplier from 0.25 to 0.0 and see if the shadow side becomes completely black.