This week I wanted a toon-style non-photorealistic render, which is something I’ve done before but not for a while, and never in Unity. I’d been playing with the Standard Shader, the physically based pipeline which has support for quite a lot of good stuff like normal / specular / occlusion maps, and kinda just wanted that plus a toon ramp. I figured I’d check out what Unity already had first.
Turns out Unity do have a toon rendering example, but it’s a custom vertex/fragment shader which doesn’t include normal mapping or any other nice features of the standard shader; I’d have to add that back.
So I started looking at how you can customise the Standard Shader to add a toon mapping lighting ramp. The first thing I checked was custom lighting in surface shaders. That was pretty useful, but again it loses all of the standard shader lighting features if you use it; you have to implement all that again.
All I really wanted was to be able to post-process the specific lighting intensity calculation and make it quantised. So I rolled my sleeves up and read the Standard Shader code end-to-end to see what I could do.
Unity don’t really provide many entry points unfortunately. However, they do
make reasonably frequent use of macros in their shaders, which are ripe for
replacement through the shader preprocessor. The closest point I could find
to the code I needed to alter was the definition of
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
At this point most of the required components have been extracted, and we just
need to compute the lighting from it. We do still need to duplicate the
internal implementations of
UNITY_BRDF_PBS but at least we don’t have to
write the whole thing from scratch or copy the entire shader code, with
all of its alternate paths for various options. It’s not perfect, but it will do.
Unfortunately AFAICT in order to cover all the same cases as the standard shader,
the actual Shaderlab definitions need to be replicated to insert my replacement,
because there is no way to “extend” a material definition; you can copy a Pass
verbatim, but you can’t copy it and prefix it with a new
#define, I don’t think.
So again, more duplication than I’d like, but less than the worst case.
I’ve put together the shader code and an example using the Ethan model from the Unity standard assets. Here’s a screenshot:
I’ve also published the code on GitHub.
You’ll find all the shader code in
Assets/Shaders, the files don’t rely on
anything but Unity’s built in shaders, the other resources are just there to
demonstrate it on a real model. The key files are:
SinbadToonSurface.shader: the Shaderlab definition of the shaders, which is a copy of Unity’s StandardSpecular.shader with some key added lines (see below)
UNITY_BRDF_PBSand has modified copies of the 3 BRDF functions Unity uses in its standard shaders
SinbadToon.cginc: the actual toon function
Although I had to copy the Shaderlab code (ugh), the only additions I needed to
make were in the lit passes, to add these 2 lines before any use of
#define SINBAD_TOON_BANDS 3 #include "SinbadPBSToonLighting.cginc"
The toon bands are procedural rather than based on a ramp texture (as I previously
would have done it), so you can tweak the number of bands to suit. If you look
SinbadToon.cginc you can also see there’s a
tolerance argument you
can use to change how sharply each band transitions to the next.
Hopefully it helps someone else!