Three.js Cel Shading with MeshToonMaterial

In Three.js, achieving a stylized, hand-drawn, or comic book aesthetic relies heavily on MeshToonMaterial. This article explores how MeshToonMaterial works, how it implements cel-shading through light banding, and how developers can customize its gradients and outlines to create a distinct 2D cartoon look in a 3D web environment.

The Mechanics of Cel Shading

Traditional 3D shading materials, like MeshPhongMaterial or MeshStandardMaterial, calculate smooth, continuous gradients of light and shadow across a surface. This realistic interpolation simulates how light behaves on physical objects.

MeshToonMaterial alters this behavior by breaking down the smooth transition of light into discrete steps, a process known as color quantization or banding. Instead of a soft gradient from bright to dark, the material renders distinct, hard-edged regions of solid color. This mimics the traditional hand-painted animation style (cel shading) where artists use a limited palette of colors to represent light and shadow.

The Role of Gradient Maps

The primary way to control the look of MeshToonMaterial is through a gradient map. By default, Three.js applies a default two-tone shading step (one level for shadow, one for light). However, developers can define custom shading steps using a small, 1D texture map passed to the gradientMap property.

To create custom steps, you define a texture containing the exact color bands you want. The critical step is configuring the texture filtering:

const colors = new Uint8Array([0, 127, 255]); // Three shading steps
const gradientMap = new THREE.DataTexture(colors, 3, 1, THREE.RedFormat);

// Prevent Three.js from smoothing the transitions
gradientMap.minFilter = THREE.NearestFilter;
gradientMap.magFilter = THREE.NearestFilter;
gradientMap.needsUpdate = true;

const material = new THREE.MeshToonMaterial({
  color: 0x3f51b5,
  gradientMap: gradientMap
});

Using THREE.NearestFilter is essential. Without it, the GPU will linearly interpolate between the pixels of your gradient map, smoothing out the edges and defeating the purpose of the toon effect.

Achieving the Comic Book Aesthetic with Outlines

While MeshToonMaterial handles the internal shading of a 3D model, a true comic book aesthetic requires dark outlines to define the edges of the object. Three.js does not automatically generate these outlines through the material itself, but they can be achieved using two common techniques:

1. The Inverted Hull Method

This technique involves rendering a second, slightly larger version of the mesh behind the original. This outer mesh is rendered with its front faces culled (hidden) and colored solid black. The result is a clean, consistent outline around the object that responds dynamically to camera movement.

2. Post-Processing Outlines

For more complex scenes, post-processing is highly effective. By utilizing EffectComposer alongside shaders like the OutlinePass or Sobel/Lapacian edge detection filters, developers can detect depth and normal differences in the rendered image and draw black lines directly onto the 2D screen space. This allows for detailed line-art styles that cover both the outer contours and the inner crevices of 3D models.