Creating a Card Shader with 3D content

This is a breadown of a couple of shaders I made for Harry Alisavakis’ Technically Speaking Discord Challenge, with the theme of Tradind Card Foil. I used Unity (URP) and Amplify Shader Editor to author these shaders, but if you are following this breakdown in order to replicate some aspects of it, you can perfectly use Shader Graph for most of it except the Stencil Buffer and some tags that you may add later to the generated code that I will also show. As a note, I greatly recommend using Amplify Shader Editor over Shader Graph as it is inmensely faster, more complete and just a joy to work with.

Creating a 3D interior with the Stencil Buffer

The most evident effect in this card shader is the 3D interior that can only be seen looking through the card. This effect is achieved using the Stencil Buffer.

The Stencil Buffer is an 8-bit mask you can use to tell shaders whether they should render a pixel or not. A shader can write to the Stencil Buffer and another shader can check that information and choose what to do or render based on that. There is some fancy stuff you can do with the Stencil Buffer but for this effect we only need to write to and check a single layer.

First, let’s set up a Unity Scene with a Quad and some sample geometry behind it, in my case two cubes. This Quad will act as a window, everything outside it will not be rendered and everything behind it will.

Create a new Unlit shader and a material and assign it to the quad. In Amplify navigate to Pass -> Stencil Buffer and activate the little box on the right.

“Reference” is the Stencil Buffer layer we are gonna use, for this project it doesnt matter what you choose, just make sure you don’t use number 0. We can also ignore Read Mask and Write Mask for the purpose of this breakdown.

“Comparison” compares the reference value to the content of the Stencil Buffer. What we want from this Shader is to write to the Stencil Buffer our reference value, doesn’t matter what the previous value was. Because of that we use “Always” so we write to it no matter what.

“Pass Front” is the action we are gonna take for the pixels in the front face of the quad. “Replace” will write our reference value into the Stencil buffer, which is exactly what we want.

If you are doing this in code, you just have to add this piece of code to the start of the Forward pass. As you see, the setting mirror the code one-to-one.

Now let’s create a new Lit Shader for the contents of our card, create a material, assign it to the cubes and navigate to the Stencil Buffer.

In this case, we want to mask every pixel that is in the mask and discard the rest. First we need to read from the appropiate layer, so we set up the reference to the same number as the other shader and set “Comparison” to “Equal” so we only act based on the information of that layer. “Pass Front” is set to Keep because we want to keep those pixels if we are inside the mask.

Once you save, it will seem as if it stopped working, but that is not the case! It is in fact discarding every pixel outside the quad and rendering only those behind it, but because the engine knows the quad is in front, it will not render what is behind it.

This is achieved with the Depth Buffer, every opaque object writes to the Depth Buffer and Unity uses that to know whether an object is in front of another, but we can tweak the behaviour of a specific shader.

Setting ZWrite to Off will make Unity render that object behind everything else, even if it is in front in the scene view.

We’ve done it!

Let’s now add the rest of the card. Add a new quad with a new Lit Shader and navigate to the Stencil Buffer Options.

In this case we want it to render only were the stencil buffer mask is not present, so we set “Comparison” to “Not Equal”.

If you orbit around the scene you may notice the effect doesn’t work at certain angles.

The Stencil and Depth Buffers are working, but we need the window shader to write to the Stencil Buffer BEFORE everything else renders, for that we need to adjust the Render Queue.

In the Foil Shader navigate to SubShader -> Tags -> Queue and increase the index by one so it always render after the window. Likewise, increase the queue index of the objects inside the card, in my case they are transparent, which by default is render after the opaque geometry.

Now that we have our scene set up and everything working, we can dive into the shaders.

Sparkles

Most effects in this card change depending on the view angle, whether it is to fake depth or just to shift colors or parameters.

Let’s start with the sparkly glitter that only shows up at certain angles. First we create a vertical band that moves from left to right depending on the view direction to fake reflection.

I learnt this effect from Cyan (@Cyanilux on Twitter), in which we just offset our UV coordinates by the View Direction. I’ve also added some more parameter to control precisely the location and potency of the effect.

Because we want a vertical band, we can use the U channel from the UVs, which is a linear gradient in the horizontal coordinate and feed it to a triangle wave node which will create a nice faded vertical band. Saturate the output to clamp it between 0 and 1 and connect it to a power node to control size and intensity. The saturate node is extremely important because the black part in the triangle wave preview can contain negative values that will play badly later on. I connected the result of this graph to the emission node so it acts as unlit color.

Now we want to color this band. For a normal foil shader you would choose to reflect different colors of the rainbow depending of the angle, I recommend you to check Alan Zucconi’s post about rainbow representation if you choose to do so (https://www.alanzucconi.com/2017/07/15/improving-the-rainbow/). In my case I settled on coloring the band based on a two-color gradient that moves along with the band.

As you can see, I use the same structure to move the UVs around and feed the U channel to the Time input of a Gradient Sample Node. You may be confused on why is says time but that is just indicating the usual purpose of the node. In reality, lower values, in our case darker colors, will be mapped to the left side of the gradient and lighter colors to the right side. We then multiply this graph with the vertical band and we have our colored band.

To make it look like glitter we will then multiply it by another graph.

To make perfectly rounded specks we use a voronoi noise and step throught it to generate a dotted mask. In addition to that, I also used a simplex noise to add some more specks with different form factors. We then multiply it by our previous result and…

Surface Pattern

Let’s disconnect our result from the master and focus now on the surface pattern.

Just as before, we start with offsetting the UV coordinates by the View Direction, but this time around I multiply it by a Gradient Noise, this will offset the UVs locally, deforming the UVs and creating interesting patterns. By multiplying it by the view direction we make the distortion dependent on it.

By using the pattern to lerp between two colors we achieve the exact same behaviour as sampling it with two-color gradient like before.

Glare

Just as before, disconect the previous graph from the master node and let’s dive into the next part.

I refer as glare to the periodical diagonal shine I added because there is never too much shiny stuff.

Because I want to make it periodical, I use a sawtooth wave connected to the time node, that way we have a linear gradient that periodically resets. We then offset the UVs by this value and rotate them to make them diagonal. Feed the UVs to a rectangle node with a very high height (or width) value and we are done.

Borders and pentagons

The graphs we created are then modulated by some procedural borders. They all follow the same structure.

Just keep in mind that if you subtract a rectangle from another you may create negative values, so be sure to saturate if needed if you see unwanted effects.

Let’s mix everything

These operations are self-explanatory. The albedo part of the card is very faint, as most of the work is done by emission and smoothness/metallic.

Starting from the left, we lerp the glitter out of the text area, in this case I settled for very faint glitter, but we could have just multiplied the glitter by the mask to completely get rid of the stars in that area. We multiply the combined colored glitter by an emission float so we can use the excess brigthness for post-processing effects like bloom. We mask the stars out of the outer edges and add the glare.

The surface pattern is also decreased in the text area thanks to a lerp, which we add to the previous result.

Finally, we increase the emission of the combined effect in the border areas, which are the sum of the outer edge, window border, pentagons and text area border.

Thanks to two lerps, we can adjust smoothness and metallic values over the text area, the pattern and the borders.

End Result

We made it! If you’ve been following, the resulting graph will look somewhat like this.

In order to achieve the final result, I added a pulsing glow in a third quad behind the card, and a fourth quad behind the cubes, as well as a white directional light and a very bright yellow point light. The text is just Text Mesh Pro with some HDR glow. To top it of, add some post-processing bloom and you will get something like this.

Conclusion

If you want more shader and game dev content be sure to follow me on Twitter: @ValerioMarty

You can also check my itch.io page for finished projects.

https://valeriomarty.itch.io/

Game Design and Development student. Game Designer/Tech Artist. @ValerioMarty on Twitter

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store