Toon Shading

17 / 17
Implement a non-photorealistic rendering technique that simulates the visual style of cartoons or comic books by quantizing light intensity into discrete color levels, often combined with edge detection.

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

ChangeEffect
Reduce to 3 tiers instead of 5Coarser shading, simpler comic look
outlineThickness to 0.2Thinner outline
edgeFactor * 0.8 to edgeFactor * 1.0Fully 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.

GLSL Code Editor

Correct Code Preview

Current Code Preview