A detailed break down of creating an animated aura effect for a sword or an object using shaders in Unity3d. The technique however is as usable for Unreal or any other environment.
There are some alternatives to the way I did it in this repo, I will discuss some at the end of the post. But first, a top down look at what we need to do to achieve this. This is a really simple effect:
- Create a plane where the pixel shader which creates the aura can run on. To do this I use Booleans in Blender.
- Create a base line for the aura effect around the sword.
- Add the waves to the base to animate it.
- Add a noise to make it swirly.
Creating the Base Plane
We need a base to run the fragment shader on. To make this, I first created a plane in Blender. Then I aligned it in a way so that the plane lays on the center of the blade. Make sure you leave a bit of space on the both end of the plane before the hilt and after tip, since we will need it.
Then we can apply a difference boolean modifier on the plane using the sword mesh, to cut away the shape of the sword in the plane.
Clean up the plane of loose geometry and triangulated the mesh for usage in Engine.
The plane is now ready to be the base of our shader. We can import it together with the sword in the engine, and everything will align perfectly. The blender plane is already unwrapped, if not make sure to unwrap it before you export.
The Base Shape of the Aura
The first thing we need is to create a coordinate system we can work with. Following Tim’s concept art I wanted to have a base shape which is always there, regardless of how the aura is animated. And for that I needed a good coordinate system.
The uv of the effect’s plane, goes from right to left (tip of the blade to the hilt) and bottom to the top.
To begin with I would like to have a coordinate system centered around the blade. That means in the Y axis, I would like to create a mirror around the blade. If you like function transformations as much as I do, you would probably use an absolute function to achieve this effect. Something along these lines.
uv.y = abs(uv.y *2.0 - 1.0);
Using this coordinate system, we can start painting a shape. The painting logic is very simple, imagine any function of x, for every input value of x, an out put value of y comes out. For every pixel, if its y coordinate is less than the out put value of the function of x, we color that pixel black. This effectively colors the area under that function of x.
So the logic could be something like this, we save the function out put in a variable named Aura On:
float AuraOn = function(uv.x)
if(uv.y < f(uv.x)) outPutColor = float4(0.,0.,0.,0.);
else outPutColor = float4(0.,0.,0.,0.);
Instead of a if condition, a step function is usually used here, with the exact same effect. It might all compile to the same thing, but this is a bit compacter.
outPutColor.xyz = step(AuraOn, uv.y).xxx;
Even better, if you want to have a smooth transition between the black and white, you can use a smoothstep:
outPutColor.xyz = smoothstep(AuraOn - blurAmount, AuraOn, uv.y).xxx;
Using the above logic, you can try a simple function, f(x) = x:
AuraOn = uv.x;
This is already a starting point. What I want though is more like a triangle centered in the middle. This is the part of shader programming, where knowing function transformations by heart is very important. For example, you can get a triangle shape by using the frac, function:
auraOn = abs(fract(uv.x + 0.5)-0.5)
You get the idea, now we have to create a function that creates the base of our aura shader around the sword. I am not going to go in depth on what I did above, if it makes no sense to you, I suggest reading a bit on shaping functions. A good place is the Book of Shaders.
Here is the final base function I came up with. The code is well documented, so you should be able to follow along.
The code above creates a shape like this:
The Wave Effect
Now that we have the base shape, we can add some waves on top of it, animate it and we would be almost done.
A popular approach is using sin or cos. If you try a code like this, you will get a basic wave shape:
auraOn = auraOn + sin(uv.y *25.) * 0.2;
If you add time to this, the waves start moving:
auraOn = auraOn + sin(uv.y *25. + Time) * 0.2;
The issue with the above wave is that it is too regular. If you overlap a few more sin or cosine curves on top, it will start looking more aura like. For example a function like this:
Would create a shape like this:
That is already a lot better and closer to the effect we want. The last part it is missing is those smooth edges, which you can get using a smoothstep and the swirls.
Fluid dynamic has this inherited rotation to it, which is central in getting the feel of a fluid, and almost impossible to get in real time. There are different ways to fake it, and some are very convincing. One of my favorite is the trick valve used for portal waters, which I used in my fog shader.
This time, I set my self a time limit as I was doing this shader. I wanted to finish the effect from start to finish under 2 hours. So I decided to go for something simple to fake it, the voronoi noise.
Based on my past experiences, voronoi is ideal for getting a cheap swirl out of this type of signed distance calculations to a function value. If in your function of x varies for different y values (you include uv.y also in your calculations), you can do interesting things, like creating shapes that intersects themselves. That means more than one y values for the same x.
To get the swirls, sample a voronoi noise, or generate one and add it to your wave calculations.
I won’t go to much details here, since the code is well documented, have a look at the shader and hopefully you will be able to follow along easily.
Bunch of stuff I would like to mention.
This shader is not optimized. Writing a shader is like painting. As you are doing it, you are still trying to figure out how the effect should look, instead of a brush, your tool is functions. I usually optimize the shader after I figure out what it is that I wanted to make. Here, I will leave that step to whoever needs it. You could get rid of 90 percent of the instructions here by baking values in different channels of a texture and simplifying the functions.
If I had more than the 2 hours I set for myself, I would probably write a brute force sign distance algorithm that bakes the distance between each pixel on the effect plane to the edge of the blade in a texture. This simplifies the shader, as you can use this to very easily create a base line for your aura shape and it would also help with coloring the aura etc.
Also, it is worth mentioning some alternatives. You could get a similar effects using particles. Although particle system has its limitations, so certain things you won’t be able to make with it, but it also has its advantages. For example you could easily make the aura effect react in a physically correct way to the change of velocity of the player hand, or other effects like it.
You don’t need the boolean step, if your sword/ object is fully opaque. Since the plane cuts through the blade, the parts that are cut out by boolean are hidden behind the opaque pixels of the sword anyways. However, if you would like to have a transparent part to the sword, such as an orb or a crystal, the boolean step ensures the rendering has no sorting artifacts.
Alternatively you can use the stencil buffer to cut out the part of the plane that is intersecting with the sword mesh in real time.
This effect looks good in certain angles, but if you look at the sword from the side, the plane won’t be visible and the effect would disappear. Solution to this is similar to the cross tree technique. Place an orthogonal plane to this one, and run the same shader on it. Here you would have to use the stencil buffer to make sure the aura doesn't occlude the sword mesh.
As usual, thanks for reading. You can follow me on my Twitter: IRCSS
- Harry’s post which inspired this one: https://twitter.com/HarryAlisavakis/status/1304367957561180160?s=20
- Kama Dagger, the dagger in the demo: https://skfb.ly/JSts
- HDR Skybox used in the demo by Greg Zaal: https://hdrihaven.com/hdri/?c=night&h=fireplace