Whoaaaa…trippy! Creating a Whirlpool Shader

I saw the whirlpool in the new hearthstone gameplay video and I was hypnotized by it for some reason. I figured this is probably a shader…but how does it work? I was determined to see how. My final result was this:

How does it work?

My shader doesn’t have the additional layers of transparency and fading that the Hearthstone whirlpool does, as I was more focused on the whirlpool effect. The basic effect is to convert our texture coordinate from Cartesian(an x/y position) to Polar coordinates(an angle and how far away it is). How far away becomes our uv’s ‘u’ and the angle is our ‘v’. This converts a straight-lined texture into something like this:

whirlpool_texture

The basic math I used was:

  float2 p = i.uv - _Pivot.xy;
 // get the angle of our point, and divide it in half
 float a = atan2(p.y , p.x ) * 0.5;
 // the square root of the dot product will convert our angle into our distance from center
 float r = sqrt(dot(p,p)); 
 float2 uv;
 uv.x = r;
 uv.y = _Pivot.z *a/3.1416;

Another example of this technique is here on Shadertoy.

The ‘Pivot’ value determines the center location and the number of times it repeats on the ‘y’. This yields a basic polar coordinate distortion:

whirlpool_polar_only

But its not animated. By changing uv.x to:

uv.x = (_Time * _Speed) - 1/(r + _Swirliness);

We can now infinitely zoom into our swirly image like so:

That’s pretty cool, but now I’d like to get it to rotate. We can add a function to rotate like so:

 float2 rotate( float magnitude , float2 p )
 {
   float sinTheta = sin(magnitude);
   float cosTheta = cos(magnitude);
   float2x2 rotationMatrix = float2x2(cosTheta, -sinTheta, sinTheta, cosTheta);
   return mul(p, rotationMatrix);
 }

And then rotate the initial UV point:

 p = rotate(_Rotation * _Time * _Speed, p);

Gives us something a little more ‘whirlpooly’

This looks pretty good, but i wanted to give it a bit more of a ‘swirl’ effect and change the velocity. So I added a small texture that looks like this:

whirlpool_mask

This is a ‘motion’ mask. I’m using only the red channel, this should probably just be the alpha channel of the image or just a separate 8-bit file. I use this to rotate the pixels more around the outside than the center. The darker the color on the map, the faster the pixels will move, meaning that the outside of the whirlpool’s velocity is higher than inside. It also distorts the image a bit (or alot depending) to give it more of a swirly pattern. I simply sample the motion mask and then use it to rotate the UV:

  fixed4 motion = tex2D(_MotionTex, i.uv);
  p = rotate(_Swirl * (motion.r * _Time), p);

Giving more of the effect that we’re after:

A  number of variables are tunable for various effects. Changing the motion mask can also modify the look as well. This can also be used for all kinds of maelstrom style effects as well.

whirlpool_shader_inspector

You can grab the shader and my example from Dropbox here.