Last time, I said I would start working on tools for content creation. While I have made progress in that area, there isn’t really a lot to show for it. I also mentioned that one of the hardest parts of making Rainbow Factory will be to recreate the art style of the show My Little Pony: Friendship is Magic, so I would like to show some of the shader work I’ve done so far.
First of all, mad props to Grant Beaudette on youtube for his incredible analysis of the shows animation and effects. This video in particular made this effect much easier to implement. He has a lot of great videos on both animation and visual effects so you should check that out.
So lets first have a look at what magic looks like in the show.
This is a screenshot from season 2, episode 10, ”Secret of My Excess”. It gives us a good look at what the telekinesis effect looks like and although the color varies from character to character, the effect still looks the same.
An implementation of the effect in Adobe After Effects is described in detail in the video linked above. All I really had to do was reinterpret it from a programmer’s point of view. Expanding the edge of a shape sounds like we need a distance field. That’s pretty straight forward, all we need to do is render a silhouette and blur it.
So first off, we have the deferred shading running mostly as usual. The only difference is that when we render geometry to the G-Buffer, we also render a silhouette of objects that are affected by magic.
Blurring this silhouette gives us a result that can be used as a distance field, where the intensity of a pixel is interpreted as the distance to the edge of the object. My implementation uses a render-target that has been down-scaled 4x to make the blur pass cheaper.
Next, we want to figure out how to interpret the distance field. Looking at the screenshot, we want the outer edge to quickly gain intensity and then slowly fall of as it approaches the object. I decided on a simple function that looks like this:
float intensity = min( d * 32, 1 ) * ( 1 - d * 0.8 );
Where “d” is the distance field sample in the range 0-1. You can see a plot of this function using WolframAlpha.
min( d * 32, 1 ) means the low values, far from the object, quickly increase the intensity, stopping when it reaches the maximum of one. Multiplying that by
1 - d would give a linear drop-off down to 0 as d gets larger and we approach the object. But we don’t want the effect to be completely transparent over the object so we simply multiply it with the amount of transparency we want. I chose 80%.
Lastly, we create the wobbly effect simply by offsetting where we sample the distance field. We do this by using two sin-function.
float2 offset = float2(
sin( time * speed + texel_pos.y * <em>frequency.x </em>) * turb_scale.x,
sin( texel_pos.x * frequency.y ) * turb_scale.y );
“texel_pos” is the position we are (originally) sampling from in pixels, “time” is… well… time in seconds, “turb_scale” controls how far out the wobbliness will reach, “speed” is how fast it will animate, and “frequency” controls how tightly packed the ripples will be.
While this effect looks great (if I do say so myself) on a screenshot there are some problems and somethings missing.
First of all, an object far away from the camera will still get the same size aura, meaning the aura will not get smaller as the object becomes smaller. To fix this we would need to blur by different amounts based on how far away the object is, or the distance field would have to be interpreted differently. While this isn’t too hard to do, I’m not sure I’ll ever get around to it. Since the camera in Rainbow Factory will always be the same distance from the player, there just isn’t need for it.
Secondly, there are no sparkles in the effect. This will be fixed when I implement particle effects.
Thirdly, and this is the hardest one to fix, the wobbly effect can move faster or slower if the object moves around on the screen, destroying the illusion of the aura being “attached” to the object, since the effect works in screen-space and doesn’t take into account the movement of the object. On solution would be to spawn a sprite or some other proxy in front of the object being affected and run the effect on that. The proxy could then move with the object and thereby solve the motion problem. But this would mean the aura of two nearby objects would not merge nicely since the two object would be animated differently. I don’t have a solution for this problem, but it may turn out to not be one. I need to continue with the game development and see if it looks “wrong”.
I had initially planned to have a video showing the effect here, but since this post is already overdue I’ll just make the video at a later time. Look forward to it.