For a long time, I wanted to create a toon shader with this cool multi-step shading. Such effect was used for example in that small game called Brawl Stars.
So how can one create such an effect himself? As always, there are a few steps to follow.
Create a graph 📊
The first step will be, of course, to create a new shader graph. For our purpose, we have to use the Unlit Shader Graph.
Let’s call it “Toon Shading Shader Graph” and open it.
Solving Light 🛋
The next step is adding shading to the unlit shader. To do it, we have to make a simple calculation. It will be a dot product of the normal vector of our mesh and light direction.
To get a normal vector in world space, we have to use the Normal Vector node with the World option selected. We can connect it to the Dot Product node.
You may notice that there is no node to get light in the Shader Graph. But that won’t be a problem!
We can use a Custom Function node. We can define its inputs and outputs as well as the code of that function.
Here we will need 2 outputs: Direction (Vector3) and Color (Vector3). The first one will be used in our Dot Product, and the second one will be used to tint the model.
Next, we have to define the body of our function. In type, please select “String” as we just want to paste our code to the node. Otherwise, we can write it to the file, but we won’t do that this time.
Our code will grab the main light for our model, and we can simply read its properties.
#if SHADERGRAPH_PREVIEW Direction = half3(0.5, 0.5, 0); Color = 1; #else Light light = GetMainLight(); Direction = light.direction; Color = light.color; #endif
You should end up with something like that:
Now we have to connect the “Direction” to the Dot Product node.
If we connected the Dot Product’s output to the Color of Unlit Master node, we would see which part of the model is lit.
Add shading 🌚
The value of the dot product ranges from -1 to 1, which we can use to add more depth to our shadow. To do that, we will use a ramp texture.
Ramp texture is nothing more than a texture with a height of 1px and a width of how much you want. In our case, I made a simple 1×16 texture with a different amount of shading.
To use it in our shader, we have to add a property, which I’ll call “RampTexture”, and it’ll be Texture2D type.
Now, we will have to normalize the output of the Dot Product to be in the range of 0 to 1. To do it, we just need to add 1 to the value and later divide it by 2.
When we have that, we have simply read the ramp texture on X-axis.
Now let’s multiply that by the light color, and we should have some promising results.
Let’s add actual texture to the model 🖼
Our model is currently missing texture, and I think we should do something about it.
Add another Texture2D property to the shader. It will be the “Base” texture for our model.
To connect that texture with our current value of the shadow, we can just multiply the value from the texture sample and the result of the ramp and light multiplication.
This should combine both values together and make our model look pretty. 💇♀️
Ready to see the result?
We did it! And I really like the result!
But it also has some problems… Even if we can make it work with Shader Graph, it’s still missing a few things. For example, we can’t make it receive shadows from other objects… 😢
So, in the end, we might still have to code it at some point… But that’s for another time 🤓
If you want to learn more, I would highly suggest taking the time and visiting these pages:
- Custom Lighting in Shader Graph: Expanding your graphs in 2019
- Making a Toon Shader with Unity! (Shader Graph tutorial)
- Shaders Case Study – Brawl Stars: Toon Shading
Other than that, let me know what you think about it in the comment section below!
If you know someone that might need this, share it with him! I would really appreciate that! 🥰
And if you are interested in getting emails when I release a new post, sign up for the newsletter!
The whole project is available at my public repository. 🔗
Hope to see you next time! 🤓