Three.js From Zero · Article s4-09

S4-09 Stylized & NPR

Season 4 · Article 09

Stylized & NPR — Toon shading, outlines, hatching, painterly

NPR = Non-Photo-realistic Rendering. Not a downgrade — a different goal. Instead of "looks like a photo," aim for "looks like a drawing." Lots of techniques, all cheap.

1. Why NPR?

Photorealism is one target. Every other target is NPR: anime, comic book, watercolor, charcoal, pixel art, blueprint diagram, Studio Ghibli clouds. Some reasons to go stylized:

  • Art direction — anime games need a specific look.
  • Performance — toon shading is dead cheap and forgiving on low-end devices.
  • Readability — diagrams, educational apps, instruction manuals.
  • Personality — stylization is a signature (Hi-Fi Rush, Spider-Verse, Return of the Obra Dinn).

2. Toon (cel) shading — the core

Standard Lambert: N·L is a continuous gradient from 0 to 1. Cel shading quantizes that into 2-5 bands.

float NoL = max(dot(N, L), 0.0);
float band = step(0.5, NoL);                 // 2-tone
// OR three tones:
float band = NoL < 0.3 ? 0.0 :
             NoL < 0.7 ? 0.5 : 1.0;
// OR via a gradient texture (ramp):
float band = texture(uRamp, vec2(NoL, 0.5)).r;

A ramp texture is a 1D gradient authored by an artist. Could be hard-edged two bands, could be a soft three-band gradient, could even include a blush of rim color. That's where MMD-style anime shaders live.

3. Outlines — two methods

a. Inverted-hull outline (cheap, robust)

Render the mesh a second time, scaled up slightly, with backface culling inverted (cull front). Paint it black. When you can see the backfaces, you're looking at the outline.

// Pass 2: outline mesh
material.side = THREE.BackSide;
material.colorWrite = true;
// vertex: position += normal * uOutlineWidth
// fragment: color = vec4(0,0,0,1);

Pro: one extra draw, no post-processing, perfect for toon characters. Con: uniform width, can misbehave on sharp concave features.

b. Screen-space edge detect (precise)

After rendering color + depth + normals, run a Sobel kernel over depth and normals. Where depth jumps or normals flip → outline pixel. Ship's bridge / diagrams.

// Compare 5 samples in a cross pattern
float dEdge = abs(d - dN) + abs(d - dS) + abs(d - dE) + abs(d - dW);
float nEdge = 1.0 - dot(n, nN) + ...;
float edge  = step(threshold, dEdge + nEdge);

4. Live demo — a head you can style

Switch between PBR baseline, two-band cel, three-band ramp, hatching, and ink/watercolor. Add or remove outline. See the cost.

5. Rim light

The edge of the silhouette glowing is a huge stylistic cue — anime uses it for every close-up. Cheap:

float rim = pow(1.0 - max(dot(N, V), 0.0), 2.0);
color += rim * uRimColor * uRimStrength;

6. Hatching (cross-hatch shading)

Instead of grey, use line density:

  1. Create 4 textures: most lines, more lines, some lines, no lines.
  2. Per pixel, compute NoL.
  3. Blend the right pair based on NoL band.
  4. Multiply against base color.

Advanced: orient hatch along principal curvature direction for a "sketchy" 3D feel.

7. Ink wash / painterly

Two tricks stacked:

  • Paper texture: multiply final color by a paper grain texture.
  • Kuwahara filter: post-process that averages pixels in the smoothest of four quadrants → smears detail into painterly blobs. Used in Borderlands promo, lots of concept-art-ish demos.
// Kuwahara (simplified 5x5)
// Sample 4 overlapping NxN quadrants around the pixel.
// For each, compute mean and variance.
// Output the mean of the quadrant with lowest variance.

8. Three.js has MeshToonMaterial

const mat = new THREE.MeshToonMaterial({
  color: 0xff6ab4,
  gradientMap: rampTex,   // 2 or 3 pixel gradient — quantizes lighting
});

That plus an inverted-hull outline = functional anime shader in 20 lines.

9. The Spider-Verse stack

Into-the-Spider-Verse rendering is actually a lot of specific tricks layered:

  • Cel shading with hard ramps.
  • Halftone dots instead of smooth shading in shadows.
  • Hand-animated "on twos" — update characters every second frame for that stop-motion feel.
  • Chromatic aberration scaled by action intensity.
  • Ink and line treatments composited in post.

They don't have one NPR shader. They have a dozen NPR passes, tuned per shot.

10. Takeaways

  • Cel shading = quantize N·L into bands. Ramp texture makes it artist-friendly.
  • Inverted hull = cheap outline. Edge-detect post = precise outline.
  • Rim light is anime's best friend.
  • Hatching, halftone, ink, Kuwahara — pile stylistic passes; don't try to find "one shader."
  • Three.js ships MeshToonMaterial for a 30-second start.