Shader Lab
20.Toon / cel shading

Toon / Cel Shading

Arc 4 · 3D Lighting · Lesson 20 of 35
coordinate signal color
0.50
0.15

Purpose

Toon shading is Lambert with a staircase — the smooth gradient becomes two flat plateaus, and every "hand-drawn 3D" look (anime, BOTW, Borderlands) is some variation on where those staircase steps land.

Key insight

Realistic shading wants continuous tone. Stylized shading wants decisions. Toon takes the Lambert value and quantizes it — rounds it off into a small number of flats. Two bands is the classic anime look: a lit tone and a shadow tone with a sharp line between them.

The placement of the threshold is the expressive lever. Pushing it toward 1 makes more of the surface fall into shadow (brooding, high-contrast). Pulling it toward 0 makes everything lit (bright cartoon). The softness of the band edge is the other lever — a razor-sharp line is graphic and draftsman-like; a slightly soft line is painterly without losing the flats.

The sliders are two orthogonal decisions. shadow-threshold is where the line is; edge-hardness is how crisp it is (here, 0 means hard step, 1 means wide painterly smear).

Break it

Shadow-threshold to 0.05, edge-hardness at 0. The object is almost entirely lit with a thin rind of shadow on the dark side. Now push shadow-threshold to 0.9: almost everything falls into shadow.

Teaches: toon thresholds decide how much of the shape reads as "in shadow" — that's a compositional choice, not a lighting one. Moving the threshold is closer to choosing a painting's key than to adjusting a light. Same geometry, same light, completely different mood.

Direct Claude

"harder shadow line" edge-hardness to 0 "softer shadow line" edge-hardness up "more shadow" shadow-threshold up "brighter overall" shadow-threshold down "add a highlight band" three-band mode (add second threshold above the first) "ink outline" separate outline pass (inverted hull or Sobel)
Meta-phrase you gain here: stylization is mostly thresholding a continuous signal. Once you see Lambert as the signal and the staircase as the stylization, you can quantize anything — specular into a hair sparkle, Fresnel into an anime crescent, distance into cel-shaded fog.
Combines with: L17 (the input is Lambert), L19 (rim often quantized too, giving an anime crescent), L18 (specular sometimes quantized to a fixed-shape hair sparkle), and outline edge detection for the full NPR stack.
the vertex + fragment shader running above
// VERTEX — standard normal pass-through
varying vec3 vNormal;

void main() {
  vNormal = normalize(normalMatrix * normal);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

// FRAGMENT — Lambert signal, then smoothstep'd into two flat bands
uniform vec3 uLightDir;
uniform vec3 uShadowColor;
uniform vec3 uLitColor;
uniform float uThreshold;    // where the band edge lands
uniform float uHardness;     // 0 = hard step, higher = painterly smear

varying vec3 vNormal;

void main() {
  vec3 n = normalize(vNormal);
  float lambert = max(0.0, dot(n, uLightDir));

  // quantize: smoothstep across a soft window centered on the threshold
  float w = max(0.001, uHardness);
  float band = smoothstep(uThreshold - w, uThreshold + w, lambert);

  vec3 col = mix(uShadowColor, uLitColor, band);
  gl_FragColor = vec4(col, 1.0);
}