Since Wizards of the Coast released their « Foil » card cosmetics on Magic the Gathering Arena, I’ve been wondering how they created these cards. This post is a step by step tutorial on how to reproduce this effect in Unity.

Effect Overview

First of all, for those unfamiliar, here’s an album with a few examples of how they look:

View post on imgur.com Three MTGA Foil Cards : Hunted Witness, Search for Azcanta & Chart a Course

And here’s an example the final result using the method described in this tutorial, with Search for Azcanta:

The effect could be summarized as a Holographic / Parallax effect. A way to implement this effect would be to ask each artist to separate the art into different layers, and then have each layer translate at a different speed. But that does seem like a huge amount of work! Couldn’t they achieve this effect with a simple shader? That would tremendously facilitate the process of foil card creation.

Also, on some cards like Search for Azcanta, you notice some distortion / movement on the water and the waterfall. We’ll add a few words on this effect at the end of this article

Anatomy of a Foil Card

It you look closely at some of these examples, you can see that there are a few visual artifacts that hint this effect is simply a distortion effect applied on the original art. Take a closer look at the first example, Hunted Witness, and focus on his right arm:

See how distorted that looks? The UV distortion does come at a price: visual artifacts

Anatomy of a foil card: Inside the red rectangle is the original art. Outside the rectangle is a complete mirror of the original art.

Unity Implementation

Step 1. Getting the mirror effect

To get the correct dimensions of the card, let’s start with a full card as a simple image in a UI Canvas, and hit the Set Native Size button to get the correct dimensions.

I used this original art of Hunted Witness as a reference. To get the mirror effect, we need to change the import settings:

Set the wrap mode to Mirror

We can now change the UI Image Sprite to the original art and get something stretched like this:

Next, we need a new material.

Create a new material and assign the Unlit/Texture shader for now.

We can now tweak the Offset / Tiling values to get the mirror. Here are the values I used:

And it gives the following result:

Pretty cool eh? We now have our base to work with. I just added 2 more UI elements, a gradient at the top and bottom (use your favorite tool, I personally use Paint.NET). The result is this:

You can now see where this is going.

I disgracefully clipped some screenshots from the game and tweaked them in Paint.NET to get this:

Feel free to add texts if you want to. I didn’t take the time to do so 🙂

Step 2. Getting the depth effect

Now we get into the gist of things: shader coding! We need to add depth to this card.

The idea is to add some kind of Depth map to the card: the closer the object, the whiter it is in the map. Here’s my take on a depth map for Hunted Witness:

Basically, the main character is in the front, and there’s a gradient from left to right

The shader I started with is a very simple diffuse shader. Here are its properties:

Properties { _MainTex ("Main Texture", 2D) = "white" {} _DepthTex ("Depth Texture", 2D) = "black" {} _DepthFactorX ("Depth Factor X", Range(-0.05,0.05)) = 0 _DepthFactorY ("Depth Factor Y", Range(-0.05,0.05)) = 0 }

And the main code is the fragment shader:

fixed4 frag (v2f i) : SV_Target { // Get the depth (0 is close, 1 is far) fixed depth = (1 - tex2D(_DepthTex, i.uv).a); // Distort uv : base uv is i.uv, simply multiply the depth by a factor for each axis float2 uv = float2(i.uv.x + depth * _DepthFactorX, i.uv.y + depth * _DepthFactorY); // Sample texture fixed4 col = tex2D(_MainTex, uv); return col; }

Change the shader associated with your material to your freshly created shader. We assign the Depth texture shown above on the material, and set the tiling to the same values than the Main Texture. We can now play with both sliders to get this effect:

View post on imgur.com Not bad for first steps.

We’re already pretty close from the effect in MTGA. We’ll now just bind mouse movement to these sliders.

I added a Foil script to my card, with this code:

public class Foil : MonoBehaviour { private static readonly int SHADER_DEPTH_X = Shader.PropertyToID("_DepthFactorX"); private static readonly int SHADER_DEPTH_Y = Shader.PropertyToID("_DepthFactorY"); private const float MIN_DEPTH = -0.05f; private const float MAX_DEPTH = 0.05f; [SerializeField] private float m_DampingFactor = 5; private float m_CurrentX; private float m_CurrentY; void Update() { float x = Input.mousePosition.x / Screen.width; float y = Input.mousePosition.y / Screen.height; m_CurrentX = Mathf.Lerp(m_CurrentX, 1 - x, Time.deltaTime * m_DampingFactor); m_CurrentY = Mathf.Lerp(m_CurrentY, y, Time.deltaTime * m_DampingFactor); Material m = GetComponent<Image>().material; m.SetFloat(SHADER_DEPTH_X, Mathf.Lerp(MIN_DEPTH, MAX_DEPTH, m_CurrentX)); m.SetFloat(SHADER_DEPTH_Y, Mathf.Lerp(MIN_DEPTH, MAX_DEPTH, m_CurrentY)); } }

All this does is find the Mouse Position on the screen, and changes the DepthFactorX and DepthFactorY float properties of the material depending on this position.

View post on imgur.com Mouse controls added

Step 3. Adding distortion on some parts of the card

Now that we have the basic effect, some cards, like Search for Azcanta, have sinusoidal distortion on some parts of the card. Let’s see how to accomplish a similar effect.

A very simple depth map for Search for Azcanta basically just tells the shader that the vintage point is from the bottom left of the card:

Yep, it’s that simple

Here’s already how it looks without any change to the shader:

View post on imgur.com Look at my Depth, my Depth is amazing

Now, we need a second mask to tell the shader where the distortion needs to happen. In that case, it’s on the river and the waterfall. Here’s the map overlayed on the actual card art:

Let’s now change the shader. In the original shader, we need to add a new sampler to get info from this texture.

Properties { _MainTex ("Main Texture", 2D) = "white" {} _DepthTex ("Depth Texture", 2D) = "black" {} _DistortionTex ("Distortion Texture", 2D) = "black" {} _DepthFactorX ("Depth Factor X", Range(-0.07,0.07)) = 0 _DepthFactorY ("Depth Factor Y", Range(-0.07,0.07)) = 0 }

And in the fragment shader, we can now sample this distortion texture and add a sinusoidal change on the UVs depending on the alpha of this texture (I hardcoded the values for the period and amplitude, could be largely improved):

fixed4 frag (v2f i) : SV_Target { fixed distortion = tex2D(_DistortionTex, i.uv).a; float2 uv = i.uv; uv.x += distortion * sin(_Time.y * 2 + i.uv.y * 30) * 0.002; uv.y += distortion * sin(_Time.y * 2 + i.uv.x * 60) * 0.003; fixed depth = (1 - tex2D(_DepthTex, uv).a); fixed4 col = tex2D(_MainTex, float2(uv.x + depth * _DepthFactorX, uv.y + depth * _DepthFactorY)); return col; }

View post on imgur.com Another test with Negate

And that’s it for me! I’m in no way an experienced Graphics programmer, so any suggestion on how to improve the effect is very welcome.

You can find my complete Unity project here.

Thanks for reading!