Toon shading (often called cel shading) is a rendering style designed to make 3D surfaces emulate 2D, flat surfaces. This style entered the mainstream with games like Jet Set Radio and The Wind Waker.
This tutorial will describe step-by-step how to write a toon shader in Unity. The shader will receive light from a single directional source, and have specular reflections and rim lighting. We will use the art style of The Legend of Zelda: Breath of the Wild as our reference, although we will not implement some of the more complex rendering techniques used in the game.
When writing shaders in Unity that interact with lighting it is common to use Surface Shaders. Surface shaders use code generation to automate the object's interaction with lights and global illumination. However, as our shader will only interact with a single directional light, it will not be necessary to use surface shaders.
The first line requests some lighting data to be passed into our shader, while the second line further requests to restrict this data to only the main directional light. You can read more about Pass tags here.
To calculate our lighting, we will use a common shading model called Blinn-Phong, and apply some additional filters to give it a toon look. The first step is to calculate the amount of light received by the surface from the main directional light. The amount of light is proportional to the direction, or normal of the surface with respect to the light direction.
The normals in appdata are populated automatically, while values in v2f must be manually populated in the vertex shader. As well, we want to transform the normal from object space to world space, as the light's direction is provided in world space. Add the following line to the vertex shader.
Right now our line of code above using the ternary operator is a step function with two steps, light and dark. To render more than two bands of shading, we will need a function with more than two steps. The simplest way to implement this is with a ramp texture.
This looks good, but the dark side is too dark; right now it is completely black. Also, the edge between dark and light looks a bit sharp, but we'll deal with that later. For now, we will add ambient light.
Ambient light represents light that bounces off the surfaces of objects in the area and is scattered in the atmosphere. We will model it as a light that affects all surfaces equally and is additive to the main directional light.
This is called a property attribute. Colors normally have their RGB values set between 0 and 1; The [HDR] attribute specifies that this color property can have its values set beyond that. While the screen cannot render colors outside the 0...1 range, the values can be used for certain kinds of rendering effects, like bloom or tone mapping.
We multiply our existing lightIntensity value and store it in a float4, so that we include the light's color in our calculation. _LightColor0 is the color of the main directional light. It is a fixed4 declared in the Lighting.cginc file, so we include the file above to make use of the value.
Before going further, we'll soften the edge between light and dark to remove the jaggedness. Right now, the transition from light to dark is immediate and occurs over a single pixel. Instead, we'll smoothly blend the value from one to zero, using the smoothstep function.
smoothstep takes in three values: a lower bound, an upper bound and a value expected to be between these two bounds. smoothstep returns a value between 0 and 1 based on how far this third value is between the bounds. (If it is outside the lower or upper bound, smoothstep returns a 0 or 1, respectively).
smoothstep is not linear: as the value moves from 0 to 0.5, it accelerates, and as it moves from 0.5 to 1, it decelerates. This makes it ideal for smoothly blending values, which is how we'll use it to blend our light intensity value.
Specular reflection models the individual, distinct reflections made by light sources. This reflection is view dependent, in that it is affected by the angle that the surface is viewed at. We will calculate the world view direction in the vertex shader and pass it into the fragment shader. This is the direction from the current vertex towards the camera.
We'll now implement the specular component of Blinn-Phong. This calculation takes in two properties from the surface, a specular color that tints the reflection, and a glossiness that controls the size of the reflection.
The strength of the specular reflection is defined in Blinn-Phong as the dot product between the normal of the surface and the half vector. The half vector is a vector between the viewing direction and the light source; we can obtain this by summing those two vectors and normalizing the result.
We control the size of the specular reflection using the pow function. We multiply NdotH by lightIntensity to ensure that the reflection is only drawn when the surface is lit. Note that _Glossiness is multiplied by itself to allow smaller values in the material editor to have a larger effect, and make it easier to work with the shader.
Rim lighting is the addition of illumination to the edges of an object to simulate reflected light or backlighting. It is especially useful for toon shaders to help the object's silhouette stand out among the flat shaded surfaces.
The "rim" of an object will be defined as surfaces that are facing away from the camera. We will therefore calculate the rim by taking the dot product of the normal and the view direction, and inverting it.
With the rim being drawn around the entire object, it tends to resemble an outline more than a lighting effect. We'll modify it to only appear on the illuminated surfaces of the object.
As a final step, we will add the ability for our shader to cast and receive shadows. Shadow casting is very simple. Add the following line of code below the entire Pass (outside the curly braces).
In order to receive shadows, we will need to know in the fragment shader whether a surface is in a shadow or not, and factor that in to our illumination calculation. To sample the shadow map cast by a light, we'll need to transfer texture coordinates from the vertex shader to the fragment shader.
We include Autolight.cginc, a file that contains several macros we will use to sample shadows. SHADOW_COORDS(2) generates a 4-dimensional value with varying precision (depending on the target platform) and assigns it to the TEXCOORD semantic at the provided index (in our case, 2).
Before we can sample the shadow map, however, we need to ensure our shader is set up to handle two different lighting cases: when the main directional light does and does not cast shadows. Unity will help us handle these two configurations by compiled multiple variants of this shader for each use case. You can read more about shader variants here. We will use a built-in shortcut to compile our variants. Add the following line of code just below the #pragma fragment frag line.
SHADOW_ATTENUATION is a macro that returns a value between 0 and 1, where 0 indicates no shadow and 1 is fully shadowed. We multiply NdotL by this value, as it is the variable that stores how much light we received from the main directional light.
Toon shaders come in a wide variety of graphical styles, but achieving the effect usually centers around taking a standard lighting setup (as we did with Blinn-Phong) and applying a step function to it. In fact, when normals and lighting data is available it can be done as a post process effect. An example of this can be found in this tutorial for Unreal Engine 4.
You can contact me about this article at Copied to clipboardr...@gmail.com ?roysta...@gmail.com ?. Sometimes I receive a lot of messages, but I'll try and get back to you as soon as I can!
I inserted the file into the white color into the white part of the shader but it just turned black. Maybe its not the correct shader? I added it from the toon window in the rendering menu. You said to use aitoon but I think I dont have it. I use Maya 2018 but I dont see any option to add AiToon.
However, I just had a look at it and this is what I would suggest. Leave the color field or the ramp shader as black and white to create the shadow. Instead, plug your texture into the Ambient Color slot under the Incandescence tab. Then under the same tab set Diffuse to 0. Should give a decent effect.
Here you go! I hope you manage to find what Ive been doing wrong. Reminder that this file was created and modified only on Maya 2018 if that helps. I attached both the psd anf the png file of the texture. Thank you for the quick replies to my posts
If you switch Color Input to Light Angle, you get the exact result you are looking for. Textures and Shadows at full brightness. But this does not work with more than 1 light! I checked out the docs and they claimed it works with all the lights in the scene. Now perhaps this is bugged, there's a very real chance they have stopped updating this node (it's old), or maybe I'm just missing something! No combination of settings would yield the result you want with more than 1 light in the scene. Sorry about this, but I just don't know enough about this node.
If you are rendering with mental ray (haven't tested this, MR is really outdated and not apart of my workflow anymore) you could use a surfaceluminance node. The result won't show up in viewport you would have to render it. And you would need to do research on how to get it working (it's really not hard). I tried finding you tutorials or shaders built for MR online, but most are so outdated as to be useless or just broken links.
You can also switch render engines. You said you didn't see aitoon (an Arnold shader) but Arnold comes preloaded into Maya 2018 onward. It's a production render engine and you will find a lot more work in the future if you learn it early (you will find next to none for MR). Here's how you load it:
c80f0f1006