This week I continued some work I started over Christmas that consisted mainly of reading through Unity’s Shader documentation and downloading a few assets from Turbosquid to play with as I start writing shaders.
I have decided on using Unity’s “Surface Shader” which combines Vertex and Pixel shaders into one, and does some clever compilation behind the scenes to create the individual vertex and pixel programs. They also allow you to use either built in lighting models or to write your own lighting functions, which I have used for the following shaders.
After a little bit of messing around set out with the goal of creating a toon/cel-shader. Toon shaders come in many forms but commonly they feature more flat shading and hard borders between light areas and shadow. I wrote two shaders: a colour ramp shader which samples a ramp texture, usually a gradient, and no diffuse texture to determine what a fragment colour should be when lit; the second shader I wrote uses hard coded light levels to create the effect while still sampling a diffuse texture.
Here you can see a a mesh on the left using a more standard Diffuse/Specular lighting model (lambert), and on the right, my modified mesh using a series of different ramps to colour the individual mesh parts. The ramp texture works by using the light’s intensity at each pixel to sample a texture, the brighter the intensity, the further left is sampled and the darker tones are found on the right.
The colour Ramp approach requires separating a mesh into multiple parts so that different materials can be applied for each ramp. You can see the individual ramps I created below and how I modified the mesh in 3DS Max
One the one hand I like how minimal and simplistic the mesh looks using just the colour ramp however as the mesh had one, I added a normal map to the shader which massively improved the look of the material. I also added it to the standard material on the left for comparison along with some colourful point lights to test how the lights tinted the surface.
Next, I replaced the Lambert light function on the standard shader with my own custom function, which hard codes 3 light levels (full bright above 50%, half bright above 25% and a quarter below that) that are multiplied by the diffuse texture to create a similar but textured effect on the left.
While i was testing the the shader on other meshes i realized that the normal maps could be too strong and cause the surface to look ugly and noisy. To fix this I added a slider to the shader that lets me set the strength of the normal map the same way as Unity’s built in standard shader.
As you can see this gives me artistic control over how the normal map is affecting the surface.
For a bit of fun and as a bit of testing i dragged over the Sponza mesh from my Rendering Engine, threw some garish point lights into the scene and slapped my Textured Toon shader onto the walls, which, to me, looks pretty cool.
I think that both shaders look interesting and give a unique feel each however they both have some technical annoyances that may get in the way. Firstly, the textured shader, while it does not require multiple Ramp textures and can use a mesh without being edited does currently have everything related to the lighting hard coded so if I want to tweak any of the settings I have to change magic numbers in the shader code. I could probably fix this by creating a grey-scale Light ramp similar to the ramped shader.
Secondly the Colour Ramped shader requires a number of individual textures to be created for a mesh, dependent on the number of colours that it requires which is a pain for this simple mesh but would be impractical for a more detailed object. However the artist can do any number of things with a ramp, including shifting the hue of the colour as it moves towards the darker shades or even use completely different colours which could lead to a number of interesting effects.
In the future I would like to delve into Unity’s GUI Editor scripts which allow you to set how the properties are laid out in a material and I imagine even add a gradient field to it which would remove the need to create the ramp textures externally.