In this article I’m going to explain the basics of creation of the stylized visual effect for games. I will use Unity Shader Graph (version 2019 LTS), so you will be able to easily adapt it for Unreal Blueprints or any other game engines.

What we are going to make?

I will use simple cloud fx as an example, but the same techniques could be applied to fire, dust, breathing, etc.

When you understand how these techniques work you will be able to scale them and adapt to different situations.

What is stylization?

When people are talking about visual effects, most of the time they refer to particle systems.

Let’s take a look at very simple particle system and see how we can make particles disappear “nicely”. I’m using this cartoon cloud texture as an example:

Cartoon cloud texture

By default particles just disappear abruptly at the end of their lifetime.

Default disappear

This doesn’t look nice, but instead feels very cheap and unfinished. Don’t do like that.

Using built-in tools that Unity provides for particle systems (Shuriken) there are a couple ways to make particle disappear a bit prettier: fade to transparency, reduce size, combination of previous two.

Fading

Fade disappear

This can be achieved using Color over Lifetime module of Shuriken.

It works quite well, especially with additive particles, so do not neglect this method. But there is nothing about stylization.

Reducing size

Reduce size disappear

This can be achieved using Size over Lifetime module of Shuriken.

This method is used quite often in cartoonish stylization. For example, you can see it in Mario games.

Mario disappear FX

But there is one more method that you cannot use right out-of-the-box. This is Dissolve effect.

Dissolving

Dissolve disappear

You can see this effect in multiple games that use cel-shading or anime games.

As I said, dissolve effect is not provided by default so we have to implement it ourselves. But first, a little bit of theory.

How does dissolve effect work?

The main thing is that pixels on the texture becomes transparent not at the same time, but in order. Dissolve might have hard edge or smooth falloff. We need to learn how to control this process and tell the shader in which order to “dissolve” pixels.

To achieve this technical artists use one of the texture channels (usually alpha) or separate texture, so called dissolve mask. For the sake of simplicity and better readability we will use separate texture.

Dissolve mask should be grayscale pattern. It can be generated procedurally (different types of noise in After Effects and Photoshop) or created manually if you want more control over dissolve process.

Here is my dissolve mask that I’m going to use in this tutorial. I took simple gradient noise, added Twirl filter and on top of that radial gradient to make central part darker, so dissolve will go from the center to borders.

Dissolve mask

Now, looking at this dissolve mask we need to understand how pixels will dissolve based on that.

The rule is simple.

The darker pixel is the sooner it dissolves. Black pixels dissolve first. White pixels dissolve last. The rest dissolves depends on shade of gray.

If it sounds hard to imagine in your head, fear not. There are tools that can help you visualize this process. For example, in Photoshop you can use Adjustment Layer with Threshold function. Moving slider left and right will give you the preview of dissolve process.

Photoshop threshold preview

When the slider in far left position this is “birth” of the particle. Far right position - the moment of particle’s “death”.

As a result we have b/w pattern that we can use as alpha channel in our shader. Black pixels will be completely transparent (alpha = 0), white pixels will be visible (alpha = 1). This will give us desired dissolve effect. After that we will need to figure out how to control this threshold value that split pixels to black and white.

It’s time to start working on the shader.

Preparation

First, let’s create new scene in Unity. Now create Quad on the scene and place it in front on the Main Camera.

Now let’s create new Unlit Graph. In the master node settings choose Surface = Transparent. Now create following properties:

  • Main Texture
  • Dissolve Mask
  • Progress Slider with [0,1] range

Initial properties

Create texture sampler for the Main Texture and connect color and alpha to the Master node.

Initial graph

Create new material based on this graph and assign it to the quad on the scene.

Quad with material

Now let’s try to replicate behavior that we have seen in Photoshop. To achieve this we will use Step function.

Step

This function receives 2 parameters:

  • VALUE - number that needs to be modulated
  • EDGE - threshold

The rule is:

If VALUE < EDGE return 0, otherwise return 1.

So the result will always be either 0 or 1. In the other words - either black pixel or white pixel. This is exactly what we need.

Step function graph

The EDGE parameter is those slider from Photoshop. In case of our shader this is going to be Progress slider. The VALUE parameter will be pixel color from dissolve mask.

But we cannot use pixel color because it is vector that contains 4 components (red, green, blue, alpha). We cannot compare vector with float value. Instead of vector we will take value of the red channel of the pixel color.

Let’s create Step node on our shader graph and connect all the parameters as described above.

Step node

Let’s temporary disconnect Main Texture’s color from Master node and connect the result of Step node instead, to check how it looks.

The output of Step node is number, but we need a vector to plug it into the Color. So first we need to create this vector using Combine node.

Step to Color

Now we can move Progress slider in the Inspector to how white square dissolves.

Inspector Step preview

Let’s bring back color from the Main Texture. Pay attention that Main texture has alpha channel as well that is used for transparency. Before testing everything together we need to take it into account.

To do so we will multiply Main Texture’s alpha with the result of Step node and plug the product to the Alpha of the Master node.

Step final graph

Now this is how our dissolve animation looks like:

Step dissolve animation

We just implemented dissolve effect with hard edges. Let’s see how we can make it smoother.

In this case instead of Step we are going to use Smoothstep function.

Smoothstep

Looking at the name of this function you may guess that it is similar to the Step and you will be absolutely right. But let’s understand what smooth- part means.

If Step function receives 2 parameters, then Smoothstep receives 3:

  • VALUE - number that needs to be modulated
  • EDGE0 - lower edge of the gradient (S-curve)
  • EDGE1 - upper edge of the gradient (S-curve)

The rule is following:

If VALUE < EDGE0 return 0. If VALUE > EDGE1 return 1. If VALUE is in-between return corresponding value on the S-curve.

What is S-curve?

If we look at formula of this function we will see the following:

// On [0; 1] interval
y = x * x * (3 - 2 * x);

Smoothstep graph

If we project it on the b/w gradient we will see this:

Smoothstep gradient

We can see that Smoothstep gradient is not linear but has some easing at the beginning and at the end (ease-in and ease-out in terms of animation).

I highly recommend this video on the channel The Art of Code where Martijn, the tech-artist from Montreal explains in detail how Smoothstep works, how you can derive it yourself and how you can use it in different scenarios.

Now let’s apply our new knowledge.

First, we need to add a new property called Falloff that will be responsible for the width of the gradient at the edge.

Falloff property For the VALUE we still will be using red channel of our dissolve mask. Now we need to decide what we will plug into EDGE0 and EDGE1.

One of them must be our Progress property. Let’s take a look at specific case. When our Progress = 0.5 we want that all pixels that have red channel value below this number were black (in other words dissolved), this means that this should be the lower edge of Smoothstep gradient. Sp Progress will go to EDGE0.

In that case EDGE1 will be Progress + Falloff.

To visualize this let again disconnect Main Texture’s color and plug Smoothstep output instead. Don’t forget to create vector out of it.

Smoothstep to color

Now we can change smoothness of the edges by dragging Falloff slider in the Inspector.

Falloff animation

But there is an artifact

If you set Falloff quite large and set Progress to 0 you still will see some dissolve, but we assume that when Progress = 0 there is no dissolve at all.

Falloff artifact

If we look closely to our formula the answer about nature of this artifact will be quite obvious. When we have Progress = 0 our upper edge of the smooth gradient is not at 0 value because we have some falloff.

We could try to change our formula and set Progress as EDGE1 and Progress - Falloff as EDGE0, but then we would have the same artifact when Progress = 1.

To fix this problem we will use another powerful tool called Remap.

Remap

This function receives 5 parameters:

  • VALUE - number that needs to be remapped
  • IN_MIN - minimum value of the original range
  • IN_MAX - maximum value of the original range
  • OUT_MIN - minimum value of the new range
  • OUT_MAX - maximum value of the new range

This function determines which percentage corresponds to the VALUE in the original range and then returns new value that corresponds to the same percentage but in the new range.

Here is an example. We have VALUE = 0.5 and original range [0; 1]. We need to remap this value to the new range [0, 2]. In this case 0.5 corresponds to 50% between 0 and 1. Now if we look at 50% between 0 and 2 (new range) we will get 1 and this is our new remapped value.

Formula to get remapped value is following:

float Remap(float Value, float2 InMinMax, float2 OutMinMax)
{
    return OutMinMax.x + (Value - InMinMax.x) * (OutMinMax.y - OutMinMax.x) / (InMinMax.y - InMinMax.x);
}

But how Remap can help us get rid of the artifact when Progress = 0?

When Progress = 0 we need all pixels to be white. It means they should be at the upper edge of Smoothstep gradient. In that case our Falloff and lower edge of the gradient go to negative values.

If we remap Progress from the original range [0; 1] to the new range [-Falloff; 1] we will get what we need.

Let’s implement it in our shader. To to so we will add Remap node.

Progress Remap

Result

Let’s bring back Main Texture’s color and check how the whole graph looks like.

Final graph

Don’t forget to set AlphaClipThreshold = 0 on the Master node for correct transparency.

Smooth dissolve

Now we have smooth dissolve effect.

What’s next?

As you might noticed I explained how dissolve effect works on a GameObject but not on Particle System. In particle systems we cannot just drag our Progress slider for each particle separately. Thankfully, Unity has built-in tools that allow us to make each particle talk with the shader and apply this effect per particle.

In the next part I will explain how to work with Custom Vertex Streams and Custom Data. We will adapt our shader to work with particle systems.

Stay tuned!


I write these articles as part of my work on King, Witch and Dragon the reversed metroidvania.

To support the project please add King, Witch and Dragon to your Steam Wishlist. To get more updates and materials about the project follow me on Twitter and Instagram.