Lately, Iâve been really into PokĂ©mon. Iâve been playing PokĂ©mon Sword and Shield, two games which are unanimously loved by the fanbase, and for the first time ever in a PokĂ©mon game I even finished the PokĂ©dex, including the Isle of Armour DLC. But thatâs not the only PokĂ©mon game thatâs been on my mind lately. Earlier this year, PokĂ©mon Mystery Dungeon: Rescue Team DX, a remake of the Game Boy Advance and Nintendo DS originals, released on Nintendo Switch, with full 3D models and a fetching hand-drawn art style. I promise this tutorial isnât just a sales pitch for the PokĂ©mon series - today, weâre going to make a shader similar to (but not exactly mimicking) the hand-drawn style of PMD.
Also check out this same tutorial over on my YouTube channel:
Drawing Shader
Some of you might be thinking: âbut Dan, didnât you do this shader already in your Image Effects series?â. And youâd be right! However, due to being an image effect, we applied the drawing texture to the entire screen instead of just an individual model. This time, I want to customise the effect for each model itâs being applied to. This gives us more control over how the drawing effect gets rendered, too; we can apply the drawing texture in screen space, which will look identical to the image effect version, or we can apply the drawings using the modelâs UV coordinates, so the pencil sketches line up with the contours of the model.
Letâs run down the components of this effect. A drawing effect like this is commonly called a âhatchingâ shader, after an art technique which involves drawing parallel lines to add shade. In PMD, the hatching effect isnât applied to every part of the image uniformly. Instead, itâs most prominent on shadowed areas of the scene, with bold black lines seen underneath Pikachu and underneath the tree in the following screenshot:
An appropriate usage of the Shocked Pikachu meme.
If you play the game or watch a trailer, youâll probably also notice that the texturing on the PokĂ©mon seems to stay static - it looks like the painting texture is applied in screen-space. Finally, most of the objects in the game have a cartoonish outline so that everything stand out. Iâm going to combine the spirit of the shadow lines with the paint texture to arrive at an effect which slathers pencil lines over whatever object youâd like. I like the idea of animating the pencil lines over time to look like the model is being sketched in realtime. Weâll start with a quick outline shader.
Geometry Outlines
There are many ways to paint outlines on objects in Unity and Shader Graph. One way, if youâre using post processing, is to run a shader that detects the edges of an object by calculating the difference in colour, normal vector or depth of the neighbouring pixels - this gets you very accurate edges, but itâs a bit harder to do in URP with Shader Graph and itâs a bit overkill for our effect. Instead, weâre going to do an age-old trick: copy the object geometry, scale it up slightly, and render only back-faces which are tinted dark.
For my examples, Iâm using a Pikachu model stolen from the Internet which already has several materials attached. Luckily, we can just add an extra material slot using the outline shader. To get started, create a new Unlit graph by right-clicking in the Project window and selecting Create->Shader->Unlit Graph. Itâs a bit more complicated than youâd think to render only back-faces in Shader Graph, because thereâs no single option to do so. First, you must select the cog menu on Unlit Master
and tick the Two Sided option. This will render both the front and back faces, so next we need to disable front-faces. We can do that by checking whether the fragment being rendered is on a front or back face and setting its alpha to 0 or 1 accordingly. For that, we have access to the Is Front Face
node, which weâll output to the Predicate field of a Branch
node. Set the True value to 0 and the False value to 1 then connect the output to the Unlit Master
Alpha field and set the Alpha Clip Threshold to 0.5, which will make front faces invisible. I think itâs a shame you canât just select back-face-only rendering as a simple option somewhere, but at least we can hack our own solution.
For some reason, this isnât a simple drop-down option anywhere, but we can get round it ourselves.
Now weâll blow up the mesh slightly. This will happen in the vertex stage of the shader, so the following code will run on each vertex of the mesh rather than its fragments. First, add a new Vector1
property to the graph using the plus arrow on the Blackboard and name it Outline Thickness
(with a default value of, say, 0.02) - this controls how many Unity units the mesh will expand by. On the graph, start by adding a Normal Vector
node in Object space, set the vectorâs magnitude to 1 with a Normalize
node, then Multiply
the result together with the Outline Thickness
property. Grab the vertex position using a Position
node, also in Object space, then Add
it to the output of the previous Multiply
. Pipe the output into the Vertex Position field on Unlit Master
, select Save Asset in the top-left corner of the Shader Graph editor, and our outline shader is complete!
It sounds a lot cooler if I say weâre âexploding the meshâ.
If we put this material on the Pikachu mesh, we get something like this. Itâs definitely good enough for our purposes, even if we could put in more work for a better effect.
Copyright infringement.
Hatching
To achieve the hatching effect, weâll use a texture I made beforehand. Weâre going to set up a timer that will let us animate the texture, then weâll sample that texture twice - one time itâll be larger than the other sample - then average them both. That texture will be used twice: to add a small UV offset to the main texture so that it looks sketched with a pencil a bit, and to add to the main textureâs colour.
Weâll kick off with a new shader called âHatchingâ - make it another new Unlit Graph (although you can make it a PBR Graph if youâd like lighting and everything will work the same). The first thing weâll add is the animation, for which weâll add a Vector1
property called Speed
(with a default of 2.5). The animation is going to set up a UV offset value that switches between a value of 0 and 0.5. Add a Time
node and Multiply
its Time output with the Speed
property, then Modulo
that value by 2. This results in a clock which rises to 2 then resets immediately to 0. However, we want the animation to switch between values with no smoothness in between, so Floor
the clock value and youâll see it blink between black and white - we used modulo 2 so that the clock spends half its time floored to 0, and the other half of the time floored to 1. Finally, Multiply
by 0.5.
Tick, tock, tick, tock.
Next, weâre going to sample the hatching texture. Before we can do that, we need a Hatching Texture
property of type Texture2D
, and two new Vector2
properties called Tiling
and Offset
, with values of (50, 50) and (0, 0) respectively. Weâre going to apply the hatching in screen space, so we start off with a Screen Position
node, but to make sure the hatching texture isnât stretched on the x-axis, we need to divide by the screenâs aspect ratio. We do this by using a Screen
node, which gives us the pixel resolution of the screen, and dividing its Height by its Width. Multiply that by the Screen Position
node. Before we use this UV to sample the texture, pass it into a Tiling And Offset
node, using the Tiling
property for the Tiling input, and the Offset
property multiplied by the result of the animation clock for the Offset input. Now we can use this in a Sample Texture 2D
node with the Hatching Texture
property! If we just use one sample, then the texture will very obviously tile, so weâll use a second sample. For this, take the output of the Tiling And Offset
node and Multiply
by, say, 0.37 (or whatever similar value looks good to you) and use that as the Sample Texture 2D
UV input - this will give us a âlargerâ texture. Use a Lerp
node with a T value of 0.5 to mix the two samples together. This gives us our final hatching value.
When I read âhatching valueâ in the context of PokĂ©mon, I have flashbacks to shiny hunting.
Now weâll apply it to the model. Weâll need three final properties: Main Texture
, a Texture2D
; Smudge Strength
, a Vector1
with a default of 0.002; and Drawing Strength
, also a Vector1
, with a value of 0.35. Smudge Strength
will control how far the UVs of the Main Texture
sample gets offset, and Drawing Strength
controls how strongly the hatching texture is overlaid onto the Main Texture
.
Start off with a UV
node to get the modelâs UVs. To this, weâre going to add an offset based on the hatching strength. Currently, the hatching value is between 0 and 1, but we want the range to be between -0.5 and 0.5, so Subtract
0.5 from it. Then Multiply
by Smudge Strength
and Add
it to the UV
node output. Use this value as the UV input to a new Sample Texture 2D
node to sample the Main Texture
- this gives us a base texture to apply to the model.
Now we want to apply the hatching value to modify the colour of the model. For that, we can use the value between -0.5 and 0.5 that we just calculated and Multiply
it by the texture sample we also just calculated. Lerp
between the pure texture sample and the multiplied texture sample in the A and B slots respectively using the Drawing Strength
in the T slot. Output that to the Color slot on Unlit Master
and weâre done! Save the asset in the top-left corner and look at the result in the Scene View.
Finally applying some Pikachu colour.
Conclusion
As with most of my tutorials, you can go a long way by just using textures in a novel and weird way. We started off by creating geometry-based outlines, which is a bit more complicated in Shader Graph and URP than youâd expect. Then, using just a basic drawing stroke texture in screen space, we modified the UVs of the base texture and the luminance of the image to arrive at an animated drawing aesthetic.
Acknowledgements
Supporters
Support me on Patreon or buy me a coffee on Ko-fi for PDF versions of each article and to access certain articles early! Some tiers also get early access to my YouTube videos or even copies of my asset packs!
Special thanks to my Patreon backers!
Gemma Louise Ilett
Jesper Kuutti
Jack Dixon John Selig Pablo Ruiz
Chris Sims FonzoUA Moishi Rand Shaun Wall
Anna Voronova Gabriella Pimenta James Poole Christopher Pereira sadizeng Zachary Alstadt
And a shout-out to my top Ko-fi supporters!
Hung Hoang Takuya Mysterious Anonymous Person