# Toon Glass Shader Breakdown

## Intro

This shader produces a toon glass effect, which involves solid diagonal lines across the quadâ€™s surface which move with the cameraâ€™s position. This is similar to how glass and window reflections are sometimes portrayed in cartoons/comics/clipart etc.

(Note : This blog post uses a different version than in the original tweet above, but the result is very similar)

## Notes

- This is an
**Unlit**shader.**Transparent**surface mode and**Alpha**blending. **AlphaClipThreshold**should be set to 0 as we don’t want to discard any pixels.- While this is mostly written for URP, I’ve also tested it in HDRP (High Definition Render Pipeline). Iâ€™m using
**Absolute World**below so that the graph can be identical in both pipelines and function correctly. However, I recommend converting this to**World**when using HDRP. This then allows us to remove the need to subtract the cameraâ€™s position, as world positions in the HDRP are already Camera Relative.

## Breakdown

### Properties

Before we begin the graph, Iâ€™m going to define a bunch of properties. Make sure you are using the same default values as the previews may look different otherwise. You can define these properties in the Blackboard, press the “+” icon, choose the type and then right-click to rename the property. Click on the small arrow next to each property name to change their settings, such as the default value. (Note : In newer versions this moved to the Shader Graph “Inspector” window).

- Color “Colour” â€“ Default : White (255,255,255) with 20 alpha.
- Vector1 “Offset” â€“ Default : 0.2
- Vector1 “Scale Multiplier” â€“ Default : 0.7
- Vector1 “A” â€“ Default : 3
- Vector1 “B” â€“ Default : 1
- Vector1 “Line Width” â€“ Default : 0.5
- Vector1 “Line Alpha” â€“ Default : 0.4

Note that in the materialâ€™s inspector, Iâ€™m using an Offset of 1 and a Scale Multiplier of 0.3. These values wonâ€™t look good in the shadergraph previews though!

I also havenâ€™t named the “A” and “B” properties very well, but they both control the number of diagonal lines produced by the shader. However A also affects how small the lines are in the center while B is more linear. The line count (per side) is equal to A*B. For best results, B should remain an integer. A doesnâ€™t have to be an integer, but A*B should be. So, A=2.5 and B=2 is fine, for 5 lines per side. A=5 and B=1 would also give 5 lines per side, but the width of the lines would be different.

### Breakdown

First we create a **Position** node set to **Absolute World** space, and **Subtract** the cameraâ€™s position (in world space, via the **Position** output on the **Camera** node) to obtain the position relative to the camera. The origin of the effect will now be at the cameraâ€™s position so it will move as the camera moves (in terms of translation, not rotation).

We can then **Transform** to **Tangent** space, which appears to make the position relative to the meshesâ€™ surface â€“ kind of? Well, at least for our purposes the **R/X** component of the position is horizontal across the surface, and **G/Y** is vertical, but there may be better ways of handling this.

Note : This is now broken as the **Transform** node normalises the **Tangent** space output which we don’t want here. You can instead use a **Matrix Construction** node with the Tangent Vector, Bitangent Vector and Normal Vector nodes connected (in that order, each in **Absolute World** space) and **Multiply** to handle matrix multiplication. Alternatively, just use the **UV** or **Position** in **Object** space but it will stretch when the GameObject is scaled.

We can **Split** to obtain these components, and **Add** them together to make a diagonal gradient. An **Absolute** function will then convert any negative values to positive. Any changes made will now be mirrored across the diagonal line as both sides share the same values.

The preview on our **Absolute** node doesnâ€™t seem to look right though. This is due to the transform to tangent space, but even if we were to change it, the preview will still show the “3D” circular version. Instead, we can create a **UV** node and put it into a **Remap** node with values of **0 and 1** for the **In Min Max** and **-1 and 1** for the **Out Min Max**. This remaps the values so the value at the center of the preview has a value of 0. When we connect this to the **Split** we can visualise the previews properly which will help a lot when creating the rest of the graph, then at the end we can reconnect the other position instead.

(In v10.3+ you can now set the Preview to 2D using the Node Settings tab of the Graph Inspector)

We now have two gradients going outwards diagonally in each direction. There are values of 0 in the center shown by the black areas on the preview. If we **Subtract** a value from this it will shift the values inwards, and output negative values in the center (which we can remove later via a **Saturate**). This results in creating a spacing between the two gradients. Here we use the **Offset property**, which will allow us to change the spacing later in the inspector. We can then also apply some scaling by using a **Multiply** with the **Scale Multiplier property**, as shown on the left below.

To handle creating multiple lines, Iâ€™m using this setup as shown above. By using a **Power** in our calculations, we can allow the values to be more exponential / less linear. This affects the width of each line based on the distance from the diagonal, as shown in the **Fraction** node preview. To better show how this works, Iâ€™ve plotted the functions here :

Both plots show the **“A” property** set to **0 to 4** and **“B” set to 1**. Note that the input on the **Power** is **A+1.01**, which Iâ€™ve rounded to **A+1**, therefore the plots show **2^x to 5^x** as labelled.

Looking at the plot on the left, When A is higher, the plot results in a more curved line.

More importantly though, the **Fraction** node (a.k.a frac function) returns the fractional (decimal) part of the number (without the integer component). This results in values only between 0 and 1 which is shown by the plot on the right. The line jumps from 1 back to 0 on the Y axis. The higher A values produce more line sections, and each line further to the right on the X axis gets smaller (takes up less horizontal space). Itâ€™s a bit of a mess but easier to see if you focus only on the green line.

We use a **One Minus** node on our second input on the **Power**, so that we input values near 1 on diagonal line rather than 0, resulting in smaller width gradients rather than larger. Thereâ€™s also a **Saturate** in place which clamps values between 0 and 1. The extra .01 on the **Add** node is in place to prevent the **Fraction** node outputting 1 in the center (the spacing created by the **Offset**) for certain integer values of A, although it can still occur if B or A*B isnâ€™t an integer, hence the mention of setting these to integers at the start of the breakdown. We could instead mask that area to always be black, but it felt a bit unnecessary.

Hopefully those plots help explain what is going on here, as the maths might not be easy to grasp by looking at the nodes alone. All we need to do now is **Step** the gradient-like result from the **Fraction** node, using the **Line Width property** to obtain solid lines which we can then **Multiply** by our **Line Alpha**. We also need to **Add** the **Color propertyâ€™s alpha**, to control the glassâ€™ surface alpha, then put this into the **Alpha** input on the **Master** node, and also set the **Color** input as shown.

Also donâ€™t forget to connect the (camera relative & tangent) position rather than the UVs at the beginning when saving the graph, as the UVs were only there for better previews while editing!

Thanks for reading! If you have any comments, questions or suggestions you can drop me a tweet or join my discord. If this post helped, consider sharing a link with others!

~ Cyan