Three.js From Zero · Article s4-09
S4-09 Stylized & NPR
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:
- Create 4 textures: most lines, more lines, some lines, no lines.
- Per pixel, compute NoL.
- Blend the right pair based on NoL band.
- 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·Linto 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
MeshToonMaterialfor a 30-second start.