https://blog.bemyak.net/favicon.icoBemyak's blog - godotYet Another Developer's BlogZola2020-05-06T00:00:00+00:00https://blog.bemyak.net/tags/godot/atom.xmlImplementing color masks in Godot2020-05-06T00:00:00+00:002020-05-06T00:00:00+00:00https://blog.bemyak.net/dev/godot-colormasks/<p>Masks are a powerful feature of different graphics editors. Can we implement them in Godot engine similarly?</p>
<span id="continue-reading"></span><h2 id="what-is-a-color-mask-and-why-do-you-need-it"><a class="zola-anchor" href="#what-is-a-color-mask-and-why-do-you-need-it" aria-label="Anchor link for: what-is-a-color-mask-and-why-do-you-need-it">đź”—</a>What is a color mask and why do you need it?</h2>
<p>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:</p>
<p>
<figure class="image">
<a href="https://blog.bemyak.net/dev/godot-colormasks//01_without_mask.png" target="_blank">
<img alt="Without mask" src="https://blog.bemyak.net/processed_images/01_without_mask.bd678da3735063c0.png"/>
</a>
<figcaption class="comment">
<em><p>Without mask</p>
</em>
</figcaption>
</figure>
<figure class="image">
<a href="https://blog.bemyak.net/dev/godot-colormasks//02_with_mask.png" target="_blank">
<img alt="With mask" src="https://blog.bemyak.net/processed_images/02_with_mask.8c17d4108e0bac89.png"/>
</a>
<figcaption class="comment">
<em><p>With mask</p>
</em>
</figcaption>
</figure>
</p>
<p>As you can see, the mask is defined by two parameters:</p>
<ul>
<li>Color (in this case it’s <code>#6798b0</code>)</li>
<li>Opacity of the mask layer (<code>33%</code>)</li>
</ul>
<figure class="image">
<a href="https://blog.bemyak.net/dev/godot-colormasks//03_result.png" target="_blank">
<img alt="Result" src="https://blog.bemyak.net/processed_images/03_result.2609590b0e381dee.png"/>
</a>
<figcaption class="comment">
<em><p>Result</p>
</em>
</figcaption>
</figure>
<p>Today, we will try to use color masks in Godot to implement the effect of <a href="https://en.wikipedia.org/wiki/Aerial_perspective">aerial perspective</a>. 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.</p>
<h2 id="creating-and-storing-a-mask"><a class="zola-anchor" href="#creating-and-storing-a-mask" aria-label="Anchor link for: creating-and-storing-a-mask">đź”—</a>Creating and storing a mask</h2>
<p>Godot already has a <code>modulate</code> property in every <code>CanvasItem</code> 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 <strong>shader</strong>!</p>
<blockquote>
<p><strong>Hot tip</strong> Shader is a tiny program that is executed by GPU.<br />
There are two major modes in which shaders operate: <em>Vertex</em> and <em>Canvas</em>.<br />
<em>Vertex</em> shader can alter an object’s form, while <em>Canvas</em> shader works on pixel-level.</p>
</blockquote>
<p>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”?</p>
<p>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 <strong>Gradient</strong>:</p>
<figure class="image">
<a href="https://blog.bemyak.net/dev/godot-colormasks//04_inspector.png" target="_blank">
<img alt="Gradient selector" src="https://blog.bemyak.net/processed_images/04_inspector.72c6c5e59c5bfd38.png"/>
</a>
<figcaption class="comment">
<em><p>Gradient selector</p>
</em>
</figcaption>
</figure>
<p>Yep, we are creating a gradient with only one component (aka step, vertical line in the center), so it is not a gradient :)</p>
<blockquote>
<p><strong>Hot tip</strong> To remove a gradient step, just right-click on it, to add a new step use left-click</p>
</blockquote>
<p>We can choose the step’s color using this selector:</p>
<figure class="image">
<a href="https://blog.bemyak.net/dev/godot-colormasks//05_gradient.png" target="_blank">
<img alt="Color selector" src="https://blog.bemyak.net/processed_images/05_gradient.4b90362f84e82e14.png"/>
</a>
<figcaption class="comment">
<em><p>Color selector</p>
</em>
</figcaption>
</figure>
<p>As you can see, it has all the needed parameters: RGB and alpha-channel.</p>
<p>Here is the shader itself, with comments:</p>
<pre data-lang="glsl" class="language-glsl z-code"><code class="language-glsl" data-lang="glsl"><span class="z-source z-glsl">shader_type canvas_item<span class="z-punctuation z-terminator z-glsl">;</span>
</span><span class="z-source z-glsl"><span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> Disable unneeded features
</span></span><span class="z-source z-glsl">render_mode blend_disabled<span class="z-punctuation z-separator z-glsl">,</span> unshaded<span class="z-punctuation z-terminator z-glsl">;</span>
</span><span class="z-source z-glsl">
</span><span class="z-source z-glsl"><span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> We'll take out the mask as input. In the editor it'll look like a gradient selector
</span></span><span class="z-source z-glsl"><span class="z-storage z-modifier z-glsl">uniform</span> <span class="z-storage z-type z-glsl">sampler2D</span> mask: hint_white<span class="z-punctuation z-terminator z-glsl">;</span>
</span><span class="z-source z-glsl">
</span><span class="z-source z-glsl"><span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> This function is used to manipulate every pixel of the texture - right what we want
</span></span><span class="z-source z-glsl"><span class="z-storage z-type z-glsl">void</span> <span class="z-meta z-function z-glsl"><span class="z-entity z-name z-function z-glsl">fragment</span></span><span class="z-meta z-function z-parameters z-glsl"><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-begin z-glsl">(</span></span></span><span class="z-meta z-function z-parameters z-glsl"><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-end z-glsl">)</span></span></span><span class="z-meta z-function z-glsl"> </span><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"><span class="z-punctuation z-section z-block z-begin z-glsl">{</span></span></span><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl">
</span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> First, we need to convert our input mask into texture
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-storage z-type z-glsl">vec4</span> mask_tex <span class="z-keyword z-operator z-assignment z-glsl">=</span> <span class="z-meta z-function-call z-glsl"><span class="z-support z-function z-glsl">texture</span><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-begin z-glsl">(</span></span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl">mask<span class="z-punctuation z-separator z-glsl">,</span>UV</span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-end z-glsl">)</span></span></span><span class="z-punctuation z-terminator z-glsl">;</span>
</span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> Then, we need to get our actual texture
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-storage z-type z-glsl">vec4</span> img_tex <span class="z-keyword z-operator z-assignment z-glsl">=</span> <span class="z-meta z-function-call z-glsl"><span class="z-support z-function z-glsl">texture</span><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-begin z-glsl">(</span></span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl">TEXTURE<span class="z-punctuation z-separator z-glsl">,</span> UV</span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-end z-glsl">)</span></span></span><span class="z-punctuation z-terminator z-glsl">;</span>
</span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> The resulting color will be a mix of thous two texture.
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> In what proportion? It depends on the alpha-channel of our mask.
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> COLOR<span class="z-punctuation z-accessor z-glsl">.</span><span class="z-variable z-other z-member z-glsl">rgb</span> <span class="z-keyword z-operator z-assignment z-glsl">=</span> <span class="z-meta z-function-call z-glsl"><span class="z-support z-function z-glsl">mix</span><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-begin z-glsl">(</span></span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl">img_tex<span class="z-punctuation z-separator z-glsl">,</span> mask_tex<span class="z-punctuation z-separator z-glsl">,</span> mask_tex<span class="z-punctuation z-accessor z-glsl">.</span><span class="z-variable z-other z-member z-glsl">a</span></span></span><span class="z-meta z-function-call z-glsl"><span class="z-meta z-group z-glsl"><span class="z-punctuation z-section z-group z-end z-glsl">)</span></span></span><span class="z-punctuation z-accessor z-glsl">.</span><span class="z-variable z-other z-member z-glsl">rgb</span><span class="z-punctuation z-terminator z-glsl">;</span>
</span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> One last thing: we should not draw mask in points
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> <span class="z-comment z-line z-double-slash z-glsl"><span class="z-punctuation z-definition z-comment z-glsl">//</span> where original texture is transparent.
</span></span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"> COLOR<span class="z-punctuation z-accessor z-glsl">.</span><span class="z-variable z-other z-member z-glsl">a</span> <span class="z-keyword z-operator z-assignment z-glsl">=</span> img_tex<span class="z-punctuation z-accessor z-glsl">.</span><span class="z-variable z-other z-member z-glsl">a</span><span class="z-punctuation z-terminator z-glsl">;</span>
</span></span></span><span class="z-source z-glsl"><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"></span></span><span class="z-meta z-function z-glsl"><span class="z-meta z-block z-glsl"><span class="z-punctuation z-section z-block z-end z-glsl">}</span></span></span>
</span></code></pre>