Beauty and the Beast — Visual Effect Graph tutorial

Bogumił Mazurek
Another Angle Games
16 min readNov 15, 2019

“Do not cite the Deep Magic to me, Witch. I was there when it was written.”

― C.S. Lewis, The Lion, the Witch and the Wardrobe

Given the wide interest in our Reddit post showing the transformation of a Japanese witch — Asuke into a daemon — Jorōgumo, I’ve decided to write this tutorial to explain how to achieve a similar result.

This is the effect that we will try to reproduce:

But before we dive into this, let me tell you about the project I’m working on…

Shadow of the Road is a turn-based RPG game taking place in medieval Japan; in a world where the industrial West is breaking into the land full of magic and mysticism. The variety of characters in our game: gods, demigods, daemons, witches - but also big, strange-looking machines powered by steam have proven to be quite a challenge for our small team. We decided to try and introduce them in an appealing way. I was tasked with handling the visual effects and I must admit - that wasn’t an easy job, especially since my colleagues have set the bar pretty high. When we started dealing with it they wanted me to breathe life into conceptual sketches. The hardest part of this task was the fact that I had to use a brand new tool, unfortunately still poorly documented.

Visual Effect Graph, which this article is mostly about, was introduced in Unity 2018.3. This node based system took the way of creating visual effects to a complete new level. Starting with flying bugs that are attracted to lights, through lightings shot from Tesla coil, even as far as colourful sparkles appearing during spells incantations or manifestation of supernatural creatures. All of that was possible with this new tool.

This tutorial includes a lots of screenshots and animations with a bit of commentary. For all of you just beginning their adventures with Unity, we will start with creating a new Unity project. VFX Graph requires a Scriptable Render Pipeline (SRP for short) in order to work — that means for now only a High-Definition Render Pipeline and Universal Render Pipeline (formerly Lightweight Render Pipeline) are compatible.

Complete project with an example scene can be downloaded from the github repository.

The following tutorial will consist of:
1) Project setup
2) Characters and animations setup
3) Custom shader creation
4) Creation of MonoBehaviour script for controlling the effect
5) VFX authoring process using Visual Effects Graph

For those of you who came here only for the sparkly thing seen on reddit — I recommend to jump right to the Shader Graph section.

Project setup

As I mentioned earlier, in order to use Visual Effect Graph, we need to set our project up based one of the SRP. We decided to go with HDRP (High Definition Render Pipeline) as support for URP launched later.

Unity team prepared some documentation for you and it can be found in the manual or on the blog. For your convenience I came up with this section on how to setup this kind of project. So let’s begin!

Firstly - download Unity — version 2018.3 or above and create a new project. Select High-Definition RP template.

Create a HD Render pipeline project

Next, open the Package Manager window and download the latest version of High Definition RP package.

Update HD Render Pipeline

Shader Graph is another tool we will need during the process as we will need to author some shaders.

Import Shader Graph

And lastly - download and install the latest version of Visual Effect Graph.

Import VFX Graph

Now we are all up to date so let’s prepare the scene where we will be testing our work. To save us some time I used the SampleScene that was already included in the template and did some clean-up by removing all unnecessary objects.

Level setup

Characters and animations

Next step is to find some characters we can work with. There are many places where we can find many interesting (and free!) ready to use models. Unity Asset Store is a good starting point to do so, but we will use the Mixamo website which offers not only characters models but also compatible animations.

MIXAMO

Because of the fact that our effect was originally meant to show transformation of a beautiful witch into a spider-daemon creature (according to the title of the article), we are going to create an effect that will transform the Beauty into the Beast.

Firstly - let’s find a model of the Beast. I chose “Maw J Laygo”, and the main reason for this is that this is probably the most recognisable (and the oldest?) model in Mixamo library. To make him less scary I’ve chosen the “Ross & Monica Gellers dance” (original name: silly dancing) animation for him.

Select the character and the animation for the Beast
Download character and animation for the Beast

Model named “Pirate by P. Konstantinov” won the casting for “the beauty” role and she has also got some kind of sensual dance.

Download character and animation for Beauty

After importing and placing our characters into Unity we need to do some basic configuration. Select your model in the project browser panel, and in the Inspector panel, go to the Materials tab. Select Import Materials checkbox, press the Extract Textures… button. Do the same for the materials using the Extract Materials button.

Material setup settings

Next, go to the Animation tab and select the Import Animation checkbox.

Setup Animation settings

In case of the pirate, our “beauty” character, set Scale Factor (under Model tab) to 2. Now, just press the Apply button to save those changes.

Mode setup settings

Now it’s time to animate our characters. In order to do this we have to create an Animation Controller first.

Create Animation Controller

Next step is to open it in the editor and create a default animation by dragging the animation attached to the character onto the opened Controller.

Set default animation in Animation Controller

The last step is to set the Animation Controller for the character in the scene.

Set Animation Controller for character

After following the steps above for both of the characters we should be able to see our characters having some fun.

Animation setup result

Custom shader

It’s time to apply some colors to our models. Normally I would use the Standard shader, but unfortunately the effect we want to achieve requires not only a custom particle system but also a custom shader as well. We need a shader that will allow us to mask some parts of our character.

Let’s use the HDRP/Lit Graph template and name it Transformation.

Create a shader from a given template.
Empty shader

Let’s create some parameters now that are present in common shaders, such as BaseColorMap, NormalMap, NormalScale, SpecularMap, AmbientOcclusionMap, EmissionColorMap and EmissionColor. The picture shown below illustrates how to set up connections for every parameter we have created.

Basic shader setup

Only Maw’s model contains emissive texture. I’ve decided to make a custom one too for the Pirate-Beauty model in order to give her a more interesting look. White dots on the texture below correspond with the buckles on clothes. Now we will be able to light them up with a red glow.

Emission texture for Beauty

The image below shows the materials setup for both characters.

Materials setup for characters
Material setup result

Now it’s time for the masking part of the shader. Open shader settings (little cog icon in top right corner of the master node) and select the Alpha Clipping option. Next create the ClippingLevel parameter. This parameter will affect the visibility of each pixel. Every pixel situated (in world-space) above ClippingLevel value will be hidden.

Alpha Clipping setup
ClippingLevel parameter
AlphaClipTreshold nodes setup
ClippingLevel result

Desintegration effect will look more interesting if we add some irregularity on the edges. For this purpose we are going to use the PerlinNoise texture. You can download it from the Internet or create your own by using Photoshop or similar software.

PerlinNoise texture

In order to use this texture, let’s create two parameters: the first one — NoiseMap, which will be our slot for this texture, and the second one — NoiseSpread, which will keep information about the size of that noise effect. We need to slightly rebuild the nodes that are attached to the AlphaClippTreshold input now.

NoiseMap and NoiseSpread nodes setup
NoiseMap and NoiseSpread result

Using a similar technique we are going to add some glow to the edges. Parameters: GlowColor, GlowSpread and GlowPower will control this effect. The one thing we need to be aware of is that we have to blend the glow color of the edges with the emissive texture we added before.

Glow nodes setup
Glow setup result
Glow setup result animation

And here are the updated settings for the materials:

Materials setup

Controlling the effect

In this step we will write a simple script that will allow us to control the effect. Additionally — the script will create a buffer that will contain current positions of the vertices of each character. This buffer will be used by the particle system where all the magic happens.

Let’s start by creating a new C# script with the name Summoner.cs. Then we’ll have to add some member variables. The most interesting ones are those related to the buffer mentioned earlier. We’ll use a Texture2D object named pointCache to store the information about the model’s vertices. The size parameter will describe the size of a uniform bounding box of our character.

Summoner.cs setup

In order to determine the size of the bounding box we’ll use a function shown below:

Summoner.cs UpdateSize

We’ll store the normalized vertex position in the pointCache Texure2D, so we need to transform world-space vertex coordinates to the normalized ones in our bounding-box space. In order to do that we can use a function that could look like this:

Summoner.cs UpdateCachePoint

Because of the fact that our shader has an option to clip/mask some parts of our model, we can do some sort of simple character swap. Additionally we’ll update our pointCache buffer every frame to let the particle system that we are going to create later use it. We will use a C# coroutine in order to not block the execution of other parts of our project while we are doing the swap.

Summon coroutine

For our convenience we’ll use a space key to run the coroutine shown above and will do some initial set up.

Summoner.cs Start and Update

This is how our script looks right now. Create an empty GameObject and add our Summoner script to it. Set up the references in the Inspector panel and you are good to go.

In the Play Mode we should now be able to run our swap coroutine after pressing the space button on the keyboard.

Summoner.cs

The expected result should look something like this:

Summoner.cs result

The actual VFX itself

Now it’s time for the main guest of the event — the VFX Graph! Let’s dive into it right away and create a new Visual Effect Graph object from the Project Browser window.

Create Visual Effect Graph object

A new window should pop up with a VFX Graph editor itself. It will contain a basic system built of 4 main sections: Spawn, Initialize, Update & Output.

In the Spawn section we can tell the particle system how many particles we want to spawn and which way we want them to go: in a bursts or constantly.

The Initialize section allows us to setup some initial (d’uh) values for each particle emitted. We can also set the capacity — how many particles our system can handle or even the bounds for our particles.

We can then change the properties of our particles — such as position, rotation, size and so on. To do this we are using the Update section of VFX Graph.

How each particle is rendered is defined in the Output section as you have probably guessed right now. We can choose from points, lines, quads or even meshes or decals to render our each individual particle.

Visual Effect Graph editor

The VFX Graph uses a Quad as the default output. Our effect requires a Line Output so we have to change it. In order to do so you can right-click on an empty space and select the “Line Output” option from a popup menu. Now just connect the Update section with newly created output node and delete the old Quad Output as shown below:

Line Output setup

In order for the particles to have some sort of a trail, we have to set the Target Offset to vector (0, -1, 0). Before we move to the next step we should add one more thing — a new block that controls the particle color during lifetime. In our case we want our particles to glow with an intense green color at the beginning and then turn bright red and eventually fade out at the end.

Target offset

We should have some sort of a fountain of sparks if we add our newly created VFX Graph to the scene like in the image below.

Time for the tricky part. We have to get the positions on our model where we want our particles to be spawned at. We’ll use the pointCache buffer that we‘ve created in the previous step. In order to access it in our VFX Graph we have to create following parameters :

  • Texture2D PointCache — reference to the buffer texture
  • Float Size — that will hold the size of the bounding box of our characters
PointCache and Size parameters

By dragging and dropping the particle system object from the Hierarchy panel onto the Project panel we can easly create the prefab. It will be useful for referencing it in our Summoner script.

Visual Effect Graph prefab
Summoner. cs and Visual Effect Graph setup

Now we have to relay the pointCache buffer data from our script to the VFX Graph. To do so we have to import our VFX namespace first.

Import VFX

Afterwards, we are going to modify the Summon method to pass the data itself. Additionally we want to sync the position and rotation values between our VFX Graph and our characters.

Summon modification

We are now ready to access and use the newly available data in the VFX Graph itself. Let’s modify the graph and the script as well (as shown below) to be sure that we are writing and reading to/from the pointCache correctly.

Point Output temporary changes
Summoner.cs temporary changes

We should now be able to see a semi-transparent sparkling ghost dancing next to the model as below. That proves our code and graph are working correctly. At this point we have achieved something really appealing that can be used in the future in a totally different way.

PointCache test result

So far so good. We can now revert the last changes and move on as they were meant only for debugging.

We are ready to relay more data between the script and the graph. Let’s take the ClippingLevel and Emit parameters and transfer them as we did in the previous step. The Emit parameter determines if the particle system should spawn the particles or not. It’s obvious that we have to create those parameters in the VFX Graph as well.

Summoner.cs ClippingLevel and Emit

Using the ClippingLevel we can hide the particles which start from the point above the value by setting their lifetime to 0.

Visual Effect Graph ClippingLevel parameter

To control spawning of the particles we will use the Emit parameter with the use of a simple Branch node.

Visual Effect Graph Emit parameter

If we would like to test our scene now, we should have something similar to the image below — our model that vanishes into thin air.

Visual Effect Graph ClippingLevel and Emit parameters result

The particles are spreading in all directions now. That is not exactly how we want them to behave. We want them to swirl and we can use the Update section of the VFX Graph to do so.

I added three forces to system:

  • the force that pulls them up and keeps them at certain height,
  • second one is the force that determines the swirl direction
  • the third force is the centripletal force that keeps the particles on the orbit.
Visual Effect Graph — new forces

I also added a new block to the Output section of our system that will set the scale of the single particle based on its speed.

Visual Effect Graph scale by speed

To improve the overall look of our effect I’ve increased the amount of the particles that is emited, resulting with something like this:

Add forces result

It’s looking pretty neat right now, but the swirl part looks a little bit too dull. Let’s bring some chaos into it.

I’ve used two techniques In order to do this. I’ve modified the forces that are added to the particles. Now every particle has its own sinusoidal orbit with a random frequency. Then I’ve added the block called Turbulence that pushes particle in a pseudo-random direction and rotates the turbulence field around the X axis.

Visual Effect Graph add some chaos

We are almost there folks, finally!

Visual Effect Graph chaos result

All we have to do now is make the particles move to the position of the character we would like to show next. Luckily we don’t have to do anything more in the Summoner.cs script as it has all we need.

When the Emit parameter is set to false, the particles won’t be spawned anymore but the pointCache buffer will still be updated. With the use of the ID property we can assign a new destination for each particle using the data from the provided pointCache. To make the particles move, we will use a new block called Conform to Sphere. Each particle will now try to conform to a different little sphere positioned on a mesh (via the pointCache). I’ve reduced the radius value of the spheres that are being generated to 0.01.

Visual Effect Graph Conform to Sphere

Ladies and gentlemen — The Final Result!

Visual Effect Graph Conform to Sphere result

There was chaos, so let there be light…

I would wrap this tutorial up at this point, but I’ll use this space as a second chance to think about this effect when I started writing this article. I’ve decided to add some extra details to the effect.

There are many particles moving on screen, glowing in many colors but they don’t affect the environment in any way. We can do something about that. Let’s add a new AreaLight object to the scene and set its color to black and the Intensity value to 400.

AreaLight

Next — create a gradient texture using a software of your choice. The steps should correspond with the color of the particles during their lifetime. Let’s import that texture into our project with the Read/Write Enabled option checked in its Import Settings.

Light color gradient
Light color texture settings

Our script will be responsible for reading the data from the texture we have just created and changing the light color itself. Let’s create some parameters to reference the Texture2D and Light objects.

Summoner.cs AreaLight and gradient texture
Summoner.cs AreaLight and gradient texture setup

The only thing we need right now is to is to write the code that will update the color of the light. It can look like this:

You must admit that with the light we added the effect looks much more realistic.

Final effect
Final effect

Phew! That was quite long, but I hope that this tutorial was fairly easy to understand. Some things could’ve been done better — for instance we could cache the buffer, but I just wanted to focus on how you can achieve this effect. It’s just one of the possible ways and you should be thinking and experimenting constantly while working with Visual Effect Graph.

I strongly encourage you to take the first step and dive into this great new tool, because as you can see, it’s not rocket science… it’s actual magic!

If you want to see how we apply the effects we wrote about here, make sure to visit our Steam page and join the Community to get the most recent updates about Shadow of the Road. Add our game to your wishlist too!

--

--