Cel shading mimics the appearance of flat, hand-drawn textures with limited color gradients and often with bold outlines around 3D objects. This effect is achieved by using:
- Toon Shading to apply stepped shading across an object, creating a gradient effect with distinct color bands.
- Outlines around the object, achieved here using a custom shader that creates a slightly scaled-up version of the geometry for black, back-facing outlines.
Step 1: Setting Up the Three.js Scene, Camera, and Renderer:
let scene, camera, renderer, control;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 15);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor("#161c21");
document.body.appendChild(renderer.domElement);
We begin by creating a basic setup with the scene, camera, and renderer, laying the foundation for the cel-shading effect. A PerspectiveCamera is chosen to bring depth and perspective to the 3D objects, giving them a sense of scale and dimensionality. The renderer is configured with a dark background color, providing contrast that enhances the visibility of both the cel shading and the outline effect. This high-contrast background helps the model’s colors and bold outlines stand out, emphasizing the cartoon-like, stylized aesthetic that defines cel-shaded rendering.
Step 2: Adding Lights:
light = new THREE.DirectionalLight(0xffffff); ambLight = new THREE.AmbientLight(0xffffff, 0.2); light.position.set(0, 3, 0); scene.add(light, ambLight);
To achieve shading, we need a Directional Light for shadows and highlights, and a dim Ambient Light for general illumination. Cel shading relies on directional lighting to create distinct bands of shading across the model, highlighting areas based on their angle to the light source.
Step 3: Loading the Toon Texture for Gradient Mapping:
let tex = new THREE.TextureLoader().load( "/Static/Textures/ToneMaps/fourTone.jpg" ); tex.minFilter = tex.magFilter = THREE.NearestFilter;
We use a four-tone gradient texture to simulate cel shading. The texture uses four bands of gray, each representing a different level of brightness, which replaces the smooth shading of traditional materials with distinct steps. By setting NearestFilter, we prevent texture blurring, maintaining sharp color bands.
Step 4: Adding Cel Shadeer Mesh
mesh = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 100),
new THREE.MeshToonMaterial({ color: "#c6e4fd", gradientMap: tex })
);
scene.add(mesh);
The cel shading effect is achieved with MeshToonMaterial, a material optimized for toon shading in Three.js. Using the gradientMap property, we apply the custom four-tone texture to control the color gradient, resulting in distinct color bands across the object surface.
Step 5: Creating the outline effect
The black outline around the object is achieved by creating a second, slightly scaled-up version of the object. We use custom vertex and fragment shaders to handle this outline rendering.
a. Vertex Shader
uniform float thickness;
void main() {
vec3 newPos = position + normal * thickness;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1);
}
In the vertex shader, the vertices are adjusted outward along their normals by a small amount, defined by the thickness uniform. This creates a “back-facing” version of the model that sits just behind the original model, producing a thin black outline.
b. Fragment Shader
void main() {
gl_FragColor = vec4(0,0,0,1);
}
The fragment shader sets the outline color to black with full opacity. This produces a solid outline around the object when viewed from the front, while the object’s original faces remain unaffected by this shader.
Step 7: Adding the outline mesh to the scene
mesh2 = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 100),
new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertShader,
fragmentShader: fragShader,
side: THREE.BackSide,
})
);
scene.add(mesh2);
This creates a duplicate of the original geometry and applies the custom shader to it. By setting the side to THREE.BackSide, we ensure that only the back-facing portion of the outline mesh is visible, allowing it to appear as an outline behind the primary mesh.
This simplified cel shader setup uses a combination of Three.js’s built-in MeshToonMaterial for easy toon shading and custom shaders to add an outline effect around the model. Here are the key takeaways:
- Cel Shading is achieved with MeshToonMaterial and a custom gradient texture, creating distinct bands of shading for a stylized look.
- Outline Effect is added using a slightly scaled-up duplicate of the geometry, rendered with custom shaders to create black, back-facing edges.
- Customization: Adjusting the
thicknessuniform in the outline shader or experimenting with different gradient textures can further customize the cel shading effect to fit different styles.
This approach allows for an efficient way to add a cel-shaded look to a Three.js scene, making it ideal for cartoon or stylized visual experiences.