Heat Distortion Explosions in Nez

The Inspiration

Again I found myself browsing twitter looking at gamedev gifs and stumbled onto a subtle effect I thought looked really awesome and was surprised I didn't see more of.

More specifically the explosions from the rockets in the gif
Imgur

Those explosions look incredible! There is a lot going on there so I took it over to https://ezgif.com/split and looked at the frame by frame. It has the classic black and white circles to give that initial pop, lots of different little smoke particles flying off and dissipating nicely, and a beautiful heat distortion that applies to the surfaces nearby. You can see some light bending of the wall right near the impacts. It's not something I've noticed before in other games with explosions like this, so it piqued my interest immediately.

As soon as I saw this I fired up a new project to play with some shaders and recreate this effect. After looking online at a few different ways to do something like this (things like noise maps + a circle gradient) I settled on a much simpler solution. The initial code came from a webgl heat distortion tutorial. The effect that tutorial showed was a little different from what we ended up building but the simple sin wave looked like it would be enough distortion if I also applied some distance functions to make it center around the explosion itself.

The Code

The shader itself ended up being pretty simple once I fixed my (several) math errors with my initial attempt:

sampler mainTex;  
float2 strongPoint;  
float frequency;  
float amplitude;  
float totalTimeMs;  
float timerMs;


float4 frag(float2 pos : TEXCOORD0) : COLOR0  
{
    float dist = distance(pos, strongPoint);
    float strength = (1 - dist) * ((totalTimeMs - timerMs) / totalTimeMs);
    float distortion = sin(pos.y * frequency * timerMs) * (amplitude * strength);
    return tex2D(mainTex, float2(pos.x + distortion, pos.y));
}

technique Technique1  
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 frag();
    }
}

That's it. The pixel shader ends up being 4 simple lines.
1. Get distance from current pixel to what I'm calling the strongPoint (this is the center of the explosion). This could be combined with step 2 but it's broken out mostly for clarity.
2. Calculate how much effect should be placed on the current pixel based on distance and time. We want the effect to dissipate as you get further away (hence 1-dist) and as time goes on (hence totalTime - currentTime / totalTime).
3. Using a simple sin wave formula, get the distortion placed on this pixel (amplitude is multiplied by strength because in this case the amplitude is sort of the intensity of the distortion).
4. Use the calculated distortion to distort the X only. We are still sampling from the main texture but using this we may sample from a pixel (or a few pixels) to the right or left.

If none of this shader stuff makes any sense, head over to my beginners tutorial for nez post processing shaders.

Now that we have our shader written, we need to think about the post processor itself. This was mostly a case of just giving you a way to set the effect parameters and then adding two functions:

public void play(Vector2 strongPoint, float totalTimeInMs)  
{
    StrongPoint = strongPoint;
    TotalTimeInMs = totalTimeInMs;
    timerMs = 0f;
    frequencyParam?.SetValue(frequency);
    amplitudeParam?.SetValue(amplitude);
    this.enabled = true;
}

public void update()  
{
    if (timerMs > totalTimeInMs)
    {
        this.enabled = false;
    }
    else
    {
        TimerMs = timerMs + (Time.deltaTime * 1000f);
    }    
}

play just gives a way to start the effect going, allowing you to set the point and how long you want the effect to last (in milliseconds). update just ticks the timer up unless we have gone over the expected time, then it disables the effect.

Only other important things to note are we want the effect disabled by default and simply turned on when its needed via play and when you pass the strongPoint in make sure it is in 0-1 format (0,0 being top left and 1,1 being bottom right in screen space) rather than in world coordinates.

The final effect could look something like this:
Imgur

Obviously this lacks the other features of those beautiful explosions by Richard Lems but it does have that one thing I haven't seen a lot of!

If you want to look at a full codebase (minus the player character/rockets) check out the github project. Let me know what you guys think on twitter (@neipo13).

Andrew Napierkowski

Professional Software Engineer | Independent Game Developer