Toon Shading
Adapted to XNA 4.0 by A. Nadif. To see the original article for XNA 3.1, follow this link.
Hi, and welcome to tutorial 7 of my XNA Shader Tutorial series. Today I will cover a simple algorithm for rendering a non-photorealistic scene using Cel shading/Toon shading, using HLSL and XNA 4.0.
To implement this effect, we need two shaders:
(a) The toon shader will add lighting based on a look-up texture, by using the diffuse algorithm covered in XNA Shader Tutorial 2 (see gamecamp.no or come to my presentations at NITH, Oslo).
(b) A post process edge detection algorithm.
First of all, we render the scene with the first shader(a) to a render target, and then we use this texture in shader (b) to find the edges and finally combining the edges with the scene:
To implement this effect, we need two shaders:
(a) The toon shader will add lighting based on a look-up texture, by using the diffuse algorithm covered in XNA Shader Tutorial 2 (see gamecamp.no or come to my presentations at NITH, Oslo).
(b) A post process edge detection algorithm.
First of all, we render the scene with the first shader(a) to a render target, and then we use this texture in shader (b) to find the edges and finally combining the edges with the scene:
Shader (a) + (b) results in the final output color.
Cel/Toon Shader
To create the cel/toon shading shader we compute the diffuse light ( N dot L ) and use this as a texture x-coordinate:
Tex.y = 0.0f; Tex.x = saturate(dot(L, N)); float4 CelColor = tex2D(CelMapSampler, Tex);
where CelMapSampler sampler for a 2D lookup texture (resolution 32x1) looking like this:
If the angle between L and N is large (dot product = 0) the texture at coordinate 0.0,0.0 will be used. If the angle between N and L is 0 (dot product = 1) the pixels at coordinate 1.0,0.0 will be used, and anything in-between will range from 0.0->1.0. As you can see on the texture, only 3 different colors is used.
By returning the CelColor from the pixel shader, the output will be the diffuse shading using the colors in the lookup texture:
By returning the CelColor from the pixel shader, the output will be the diffuse shading using the colors in the lookup texture:
But we want textures as well. This can simply be done in the same way as in Shader Tutorial 2, but instead of using the diffuse color multiplied with the texture color, we can multiply the texture color with the toon-shaded diffuse map CelColor:
return (Ai*Ac*Color)+(Color*Di*CelColor);
The result of this shader will look like this:
Not very hard eh? :)
Ok. This looks pretty good and can be used. But in some cases, one might want to have outlines like many drawings have.
- One approach to this is by first rendering the culled version of the object totally black, and then rendering the cel-shaded version of the object, but a tiny bit smaller,
- Another approach is to render the scene to a texture, and applying post process edge detection shader to the scene. This is how we are going to do it in this tutorial.
Ok. This looks pretty good and can be used. But in some cases, one might want to have outlines like many drawings have.
- One approach to this is by first rendering the culled version of the object totally black, and then rendering the cel-shaded version of the object, but a tiny bit smaller,
- Another approach is to render the scene to a texture, and applying post process edge detection shader to the scene. This is how we are going to do it in this tutorial.
Post Process Edge Detection
The edge detection shader in this shader is a kernel filter with the following matrix:
A kernel filter works by applying a kernel matrix to every pixel in the image. The kernel contains multiplication factors to be applied to the pixel and its neighbors. Once all the values have been multiplied, the pixel is replaced with the sum of the products. By choosing different kernels, different types of filtering can be applied.
Applying this shader to a texture will create a black and white texture, where the edges are black and the rest is white.
Applying this shader to a texture will create a black and white texture, where the edges are black and the rest is white.
This makes it very easy to create combine the normal scene with the edges:
Color*result.xxxx;
Where Color is the scene texture, and result.xxxx is the edge detection texture.
When multiplying Color with result, all pixels that is not a part of an edge is white (in result) and is the same as 1.0, and edges is 0.0 (black). Multiplying Color with 1.0 equals color, and multiplying color with the black edges (0.0) will result in a 0.0 (black color).
When multiplying Color with result, all pixels that is not a part of an edge is white (in result) and is the same as 1.0, and edges is 0.0 (black). Multiplying Color with 1.0 equals color, and multiplying color with the black edges (0.0) will result in a 0.0 (black color).
As you can see, toon shaders is not very hard and can easily be implemented in any scenes with a few lines of code. If you haven't yet done so, download the executable files and the source, and start experimenting!
Download Sample
Download Sample