Implementing color masks in Godot

⏳ 3 min read📅 2020-05-06

Masks are a powerful feature of different graphics editors. Can we implement them in Godot engine similarly?

🔗What is a color mask and why do you need it?

In many graphics editors (e.g. Krita, Photoshop) there is a thing named color mask. This is a convenient way to change the color of your image or parts of it, trim the image into a specific shape, etc. Consider these images with the mask off and then on:

Without mask

Without mask

With mask

With mask

As you can see, the mask is defined by two parameters:

  • Color (in this case it’s #6798b0)
  • Opacity of the mask layer (33%)
Result

Result

Today, we will try to use color masks in Godot to implement the effect of aerial perspective. This is a very useful effect as it enables you to save some resources since you don’t need to have separate sprites for “close” and “far” layers. Take a look at the image above: this effect makes grass on the background look like it is further away and the Player will treat it like “inactive” and won’t try to walk on it.

🔗Creating and storing a mask

Godot already has a modulate property in every CanvasItem instance, but there is a problem with alpha-channel: if we set it, then the whole layer becomes transparent instead of just setting the “mix ratio”. To fix this we will write a shader!

Hot tip Shader is a tiny program that is executed by GPU.
There are two major modes in which shaders operate: Vertex and Canvas.
Vertex shader can alter an object’s form, while Canvas shader works on pixel-level.

Again, we want to “mix” the colors of the original image with the colors of the mask. Image Colors are stored in Texture that we will iterate through pixel-by-pixel during shader execution. But where should we keep the “mask”?

Luckily, shaders have inputs, so we can feed it with the needed info. The most effective storage for a “mask” that I found is a Gradient:

Gradient selector

Gradient selector

Yep, we are creating a gradient with only one component (aka step, vertical line in the center), so it is not a gradient :)

Hot tip To remove a gradient step, just right-click on it, to add a new step use left-click

We can choose the step’s color using this selector:

Color selector

Color selector

As you can see, it has all the needed parameters: RGB and alpha-channel.

Here is the shader itself, with comments:

shader_type canvas_item;
// Disable unneeded features
render_mode blend_disabled, unshaded;

// We'll take out the mask as input. In the editor it'll look like a gradient selector
uniform sampler2D mask: hint_white;

// This function is used to manipulate  every pixel of the texture - right what we want
void fragment() {
    // First, we need to convert our input mask into texture
    vec4 mask_tex = texture(mask,UV);
    // Then, we need to get our actual texture
    vec4 img_tex = texture(TEXTURE, UV);
    // The resulting color will be a mix of thous two texture.
    // In what proportion? It depends on the alpha-channel of our mask.
    COLOR.rgb = mix(img_tex, mask_tex, mask_tex.a).rgb;
    // One last thing: we should not draw mask in points
    // where original texture is transparent.
    COLOR.a = img_tex.a;
}