[SOLVED] How to mask/clipping a GUI?

I don’t quite understand the question, but I have many GUI layers in my project. We are making a very simple novel game and we use GUI for everything, even for the background, which is why it is impossible to render the GO, because the GUI is rendered last, but I think in the render, draw the GUI first, then the GO should show

Your post mentioned “stencil”, which is how the GUI layering system is built. It would be more technical to solve your masking if you need it to work exactly like a layer does in the GUI. Implementing the masking part of the look is easier as you can use a texture to do the actual masking. Would that solve your issue?

Instead of a single texture, its show the whole atlas and also in black and white.
dmengine_HX9lDvsTCp

It’s all fixable, but I need more than just a texture, I need to show complex animation through the heart window (stencil), so I give up. My issue can be solved only by the knowledge and skills of shaders and rendering. I think I just realize what I want with frame-by-frame animation…

And all of this needs to be in gui, or would it work with a go solution? Let’s see if we can’t help you with it, would be a good example I think

1 Like

I’ve just updated my above example to add GUI mask demonstration. Although it’s quite tangled, I hope you can find something useful.

1 Like

Yes, something you could make with Photoshop as I’m also using for my project but it quite costs

Oh, thank you so much, can I just ask why he makes the picture black and white? I added pink and green, but they turned out white

This is alpha masking technique, so the mask is always in black and white. The white will make the main texture visible while black will make it hidden.

I may know what you are looking for. Actually you can modify a bit to get what you want

Yes, I got it, thank you very much :smiling_face_with_three_hearts:, that should solve my problem

4 Likes

Thanks everyone, I want to share the result I wanted to achieve. I would also like to make different speeds for the waves, but it is quite difficult, because the mask has coordinates relative to the texture to which it is attached, I tried to add a separate coordinate of the var_texcoord1 for the mask through the shader, but as with the Positions, this is technically not implemented in the GUI or I didn’t understand why it didn’t work.
Thanks again for your help! :blush:
dmengine_sUrZY4JSnI

6 Likes

To archive the result as you wanted, if you still use the code in my example, you can change a little bit at fragment shader program:
From:

vec4 tex = texture2D(texture_sampler, var_texcoord0.xy);
vec4 masky = texture2D(mask_texture, var_texcoord0.xy - mask_position.xy);

To:

vec4 tex = texture2D(texture_sampler, var_texcoord0.xy - mask_position.xy);
vec4 masky = texture2D(mask_texture, var_texcoord0.xy);

I did as you said but it gave this result GIF, now no matter how i change the mask_position nothing changes.
dmengine_jIKvWr70um

My goal is for the mask to always stay in one position on the screen and the waves to move up and down and slightly left and right.

For now, I implemented the moving heart mask down, and the waves up with the same speed, this way I make the “standing effect” of the mask in a static position:

function update(self, dt)
local speed_y = 5
local speed_x = 10
self.mask_postion.x = self.mask_postion.x - 1/speed_x * dt
self.mask_postion.y = self.mask_postion.y - 1/speed_y * dt -- move down
msg.post("@render:", "set_mask_position", { position = self.mask_postion })

self.current_pos.x = self.current_pos.x + 128/speed_x * dt
self.current_pos.y = self.current_pos.y + 128/speed_y * dt -- move up
if self.current_pos.y >= self.origin_pos.y - 30 then
	self.current_pos.x = self.origin_pos.x
	self.current_pos.y = self.origin_pos.y - 128
	self.mask_postion.x = 0.35
	self.mask_postion.y = 1
end
gui.set_position(self.wave_1, self.current_pos)

end

You do not set position of the node. After set the fragment sharder as in my above, removing the code bellow the line msg.post("@render:", "set_mask_position", ...) might help.

Yes, that helped, managed to achieve the same result, but without the complex calculations, thank you very much for your help :blush::heart:
dmengine_M0csmNiGC4

4 Likes

I need help again :pensive: I’m trying to implement different movement logic for two masks, but only the last wave_2 works, more precisely, wave_1 also works, but according to the movement logic of wave_2. I understand why this happens, but I don’t understand how to fix it :disappointed_relieved:

dmengine_GYyB4W26NH

-- render GUI
--
local camera_gui = state.cameras.camera_gui
render.set_view(camera_gui.view)
render.set_projection(camera_gui.proj)
render.enable_state(render.STATE_STENCIL_TEST)

if self.maskable_nodes["wave_1"].mask_sampler then
    if self.maskable_nodes["wave_1"].mask_position then
        if not camera_gui.frustum.constants then camera_gui.frustum.constants = render.constant_buffer() end
        camera_gui.frustum.constants.mask_position = self.maskable_nodes["wave_1"].mask_position
    end
    render.enable_texture("mask_sampler", self.maskable_nodes["wave_1"].mask_sampler)
end
render.draw(predicates.gui, camera_gui.frustum)
if self.maskable_nodes["wave_1"].mask_sampler then
    render.disable_texture("mask_sampler")
end

if self.maskable_nodes["wave_2"].mask_sampler then
    if self.maskable_nodes["wave_2"].mask_position then
        if not camera_gui.frustum.constants then camera_gui.frustum.constants = render.constant_buffer() end
        camera_gui.frustum.constants.mask_position = self.maskable_nodes["wave_2"].mask_position
    end
    render.enable_texture("mask_sampler", self.maskable_nodes["wave_2"].mask_sampler)
end
render.draw(predicates.gui, camera_gui.frustum)
if self.maskable_nodes["wave_2"].mask_sampler then
    render.disable_texture("mask_sampler")
end

render.draw(predicates.debug_text, camera_gui.frustum)
render.disable_state(render.STATE_STENCIL_TEST)
render.disable_state(render.STATE_BLEND)

That’s why I’m not happy with this solution. You can just use it very limited.

If you want to use it for 2 different nodes in the same GUI scene then I think you can clone the material to a new one with different constant name and mask texture name. And in render script you do not double render.draw(predicates.gui...) line, just double others (with different names)

Hope you understand what I’m saying :grinning_face_with_smiling_eyes:

Also consider to use frame-by-frame animation if all you need is just that heart. It will be more clearly and easy to work with

3 Likes

Hi again, I made a friendly component Mask via Druid to make it easier to use the mask.

In particular, it is now easy to make animations through Panthera 2.0, because I added a mirror nodes that “imitates” the behavior of node, but be careful here! Mirrored only position and scale. Happy coding :blush:

druid-component-masking-example.zip (448.2 KB)

8 Likes

Looks much more convenient. Thanks for sharing the component!

To Defold team: Just like Slice9, masking is popular in every game engines. Please consider to support it as a built-in way, make it easier to developers.

3 Likes


If the sprite’s width height is bigger than the mask’s size then this occurs how do I solve this?

After fiddling a lot with the fragment code I came up with this solution .

gui_maskable_1.fp

varying highp vec2 var_texcoord0;
varying lowp vec4 var_color;

uniform lowp sampler2D texture_sampler;
uniform lowp sampler2D mask_sampler_1;
uniform lowp vec4 texture_position_and_scale_1;
uniform lowp vec4 mask_position_and_scale_1;
uniform lowp vec4 mask_size;

void main()
{
    // Sample textures
    vec4 tex = texture2D(texture_sampler, (var_texcoord0.xy - texture_position_and_scale_1.xy) / texture_position_and_scale_1.zw);
    vec4 masky = texture2D(mask_sampler_1, (var_texcoord0.xy - mask_position_and_scale_1.xy) / mask_position_and_scale_1.zw);
    
    // Calculate alpha multiplier
    float alpha_mul = 1.0 - var_color.w * (1.0 - masky.a);
    
    // Check if fragment is within mask bounds
    bool isWithinMaskBounds = all(greaterThanEqual(var_texcoord0.xy, mask_position_and_scale_1.xy)) &&
                              all(lessThanEqual(var_texcoord0.xy, mask_position_and_scale_1.xy + mask_size.xy));
    
    // Discard fragment if outside mask bounds or mask alpha is too low
    if (!isWithinMaskBounds || masky.a < 0.1) { // Assuming 0.1 as the threshold for visible alpha
        discard;
    }
    
    // Apply color and alpha multiplier
    tex *= (alpha_mul * masky.r * var_color.w);
    gl_FragColor = tex * var_color;
}

and in the render script you must pass the size in normalized form

camera_gui.frustum.constants["mask_size"] = vmath.vector4(gui_maskable.mask_width / gui_maskable.texture_width, gui_maskable.mask_height / gui_maskable.texture_height, 0, 0)