2D UV distortion shader in Godot 2


Godot 2 doesn't have many built-in effects, but thankfully it has a beautifully simple shading language. This article explains how to create a 2D UV distortion shader.

The following article is just a short explanation of the project. For details check out the project files.


Basic idea

There are several ways to animate things like grass or fire in 2D, but they ususally involve quite a bit of work by the artists. One way to create an animation is to write a shader that offsets the pixels of a sprite in a wave-like pattern.

The offset

So step 1 is to calculate that offset. We could use some kind of noise function for that, but it's more efficient and more versatile to store random values in a texture and read them from there. For example in this demo I have faded the random values to black in the bottom half of the texture, so the offset will only affect the upper half of the sprite (since black equals the color value 0). The result is that only the upper half of the grass will move in the wind and the part near the roots will be affected much less.


The first line in the shader declares that noise texture so it can be assigned in the material. Next we read the color of one pixel from that texture using "tex(noiseTex, UV)" - but in order to animate the offset we add "vec2(TIME, 0)" to the UV coordinate. The result is that now the noise texture moves over the sprite from right to left once per second. If you want to change the animation speed just multiply TIME with the desired speed.
Since in this demo I used a greyscale texture I only read the red channel (by appending ".r" at the end of the line), but if you need 2d offsets you can easily create separate noise patterns in each color channel and read the red and green channel by appending ".rg" instead.

Applying the offset

The second and last step is to apply the calculated offset when reading a pixel from the sprite texture. Since the noise value read from the noise texture ranges from 0 to 1 I multiply it by vec2(0.02,0), which means that a white pixel in the noise texture results in an offset of 0.02 times the sprite width in the horizontal.
For the sake of simplicity in this article I have kept this value hardcoded, but you will probably want to store it in a parameter instead so you can modify it for different sprites.
Now that we have the offset vector we just need to read the pixel from the sprite texture, but instead of reading at the coordinate "UV" we read at "UV + offset", thus distorting the texture.