Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "Mask2D" node (and/or a "mask_parent" property for Sprite2D) #4282

Open
Tracked by #7
golddotasksquestions opened this issue Mar 25, 2022 · 111 comments · May be fixed by godotengine/godot#74859
Open
Tracked by #7

Add a "Mask2D" node (and/or a "mask_parent" property for Sprite2D) #4282

golddotasksquestions opened this issue Mar 25, 2022 · 111 comments · May be fixed by godotengine/godot#74859

Comments

@golddotasksquestions
Copy link

golddotasksquestions commented Mar 25, 2022

Describe the project you are working on

This applies to all 2D projects I have worked on in the last 3,5 years, so ever since I started working with Godot.

Describe the problem or limitation you are having in your project

Ever since I started using Godot, 2D masking is one of these fundamental basic areas which has given me the most trouble.

This is something most people who hear the praise for Godots great 2D support would think already exists as an extremely simple and straight forward solution. But very unfortunately, the opposite is the case: The existing solutions are extremely cumbersome and counter-intuitive to use, require a high skill level and often very performance hungry as well.

They are in fact so complicated and hard to explain to a beginner it I actively try to avoid doing so. Even explaining this process is no fun.

Basically you have two options: use Light2D with Mask mode or write a Shader.
Neither of them offer the same intuitive simple use as a Sprite with texture. Light2D has it' own crazy collision-layer type system over which Light2D affects which other canvas item. This might make sense for a 2D light, but for a Sprite Mask this is extremely counter productive. Light2D nodes are also really performance hungry, especially on lower end mobile hardware.

Shaders on the other hand are more performance friendly, but are a huge step up in the difficulty curve, especially for those who are not already familiar with a C-like language. Trying to position a shader texture in world coordinates is a nightmare! For programming beginners this makes the shader option a complete nogo. Even after learning 2D shaders more than for 2 years now, I'm still not at the point where I can position a 2D mask texture in a shader as intuitively and freely as I can position Sprite anywhere on Canvas.

Edit: Light2D mask modes does not seem to exit in Godot4 any more. Apparently CanvasGroup is supposed to fill this void, but since it act as a parent of two nodes falls short for this usecase in principle, as well as for many of the same reasons as Light2D does (which I listed in detail in here)

My point is: We really need a proper simple and intuitive solution for this which makes adding a 2D mask and positioning it anywhere as simple, easy and intuitive as adding and positioning a Sprite.

With Godot 4 the "clip_children" property was introduced. This allows 2D parent nodes to mask their children, similarly like it was already possible in Godot 3 using the rect_clip_content property of Control type nodes or the VisualServer canvas_item_set_clip(), but this time on a fragment (per pixel) basis.

This is a useful addition! Unfortunately it has been broken ever since it was first implemented, and still is in the current Godot 4.0 Alpha5 version. godotengine/godot#49198 (comment)

More unfortunately, however, is how even if it would be working, enabling clip_children does not solve the need for simple straight forward intuitive masking and positioning it in an equally simple and straight forward way.

We need a Node2D type of node just like the Sprite with a texture slot, but when it's parent is a 2D canvas item, it will mask this parent with every not transparent pixel (which color this pixel is is irrelevant, only the alpha channel is relevant).

It would look like this:
mask2d

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Usage would be intuitive, straightforward and dead-simple:

  1. Add a Mask2D node as child to a Sprite or any other 2D node,
  2. give it a texture and position it where ever you please,
  3. done!

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

mask2d_2
mask2d_3

Edit2: Multiple Mask2Ds:
mask2d_4

mask2d_2.mp4

Edit3: difference between the existing clip_children and Mask2D / mask_parent property of this proposal:

mask2d_03

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, right now the opposite is the case. Doing this is very cumbersome and definitely not as straight forward as it should be.

Is there a reason why this should be core and not an add-on in the asset library?

It is desperately needed basic 2D functionality which needs to be equally basic and easy to use.

@Calinou
Copy link
Member

Calinou commented Mar 25, 2022

For programming beginners this makes the shader option a complete nogo. Even after learning 2D shaders more than for 2 years now, I'm still not at the point where I can position a 2D mask texture in a shader as intuitively and freely as I can position Sprite anywhere on Canvas.

This can surely be made more convenient by an add-on which provides its own editor gizmos for placing masks.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

@Calinou have you read the proposal? This is feature already exists in Godot, it's just terrible to use. Addons are always additional steps, and their discoverbility is by design worse.

Adding gizmo or tool or anything I have to learn extra is the opposite of what I am proposing here.
From a user perspective, with a Mask2D I would need to learn or download nothing extra. If I know how to use a Sprite I know how to use a Mask2D node. They work exactly the same way, only one "adds a texture" and the other "removes from a texture".

This is how masks work in any other software or visual editor.

Trying to solve this by adding more steps (as addon) completely defeats the purpose of this proposal of adding and positioning a mask in an quick and intuitively discoverable way.

If adding a separate node for this is undesired, adding this functionality as property to Sprite2D nodes would be a fix. As I mocked up here:
https://user-images.githubusercontent.com/47016402/160154896-65e58e89-2eb9-470a-a0b4-e40868b39fe6.png

But an having this basic 2D functionality as addon makes no sense whatsoever.

@fire
Copy link
Member

fire commented Mar 25, 2022

As far as I know this is implemented by CanvasGroups.

https://godotengine.org/article/godots-2d-engine-gets-several-improvements-upcoming-40

@golddotasksquestions
Copy link
Author

@fire I know about CanvasGroup. How is it equal to the Mask2D I am proposing here?

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

I repeat it here: The point of this proposal is to make 2D masking as easy as adding and positioning a Sprite node.
If not via a distinct node, give Sprite a mask_parent property. If that parent happens to be a CanvasItem, the parent gets masked.

@TheDuriel
Copy link

This is already implemented in Light2D.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

@TheDuriel

Exactly.

If you read it again you will notice I have stated so. This feature exists, it's just not easy to use or very practical either as shader or the Light2D implementation. Light2D are also really bad on mobile hardware performance. Multiple Light2Ds in mask mode don't work together. You have to use an inverted texture which is super counter intuitive (transparent pixels in the Light2D do the masking)
Shaders seem to be much less performant hungy, but such a shader is so much more complicated to implement, I don't manage to write such a shader despite learning Godot shaders now for more than two years. Masking with the shader is not so much the issue here as positioning.

@TheDuriel
Copy link

An example for a masking shader is, iirc, given in the docs somewhere. If not, well its only two actual lines of code. All you need is a texture uniform and then use any of its channels as the alpha. Its basic enough in any case that it should likely be noted as a tutorial if it is not already.

The light2D implementation does require fiddling with mask properties, granted, but a developer in need of this feature could extend the class easily enough to provide some exported properties to make it easier.

@insomniacUNDERSCORElemon

I'm biased, but I like the idea of this being integrated into the clip workflow (working with any canvas items). I believe I've suggested (somewhere) a clip mode option before that would allow some items to be marked as unclippable by parent (to allow for more robust characters to be made with 1 logical tree) as well as clips parent. Unfortunately I don't know if that'd be a viable setup with how the tech works, assuming it can be even fixed to work to its full existing potential that it started with.

Also with clipping you could have a parent polygon and simply alter the shape and have that state saved via an animation player (swapping to it when needed) but I definitely agree that creating a shape to extract (and then just turning visibility on when needed!) is significantly easier and better. Especially when/if you need to edit/swap it out later (even just say, moving up a bit), if you need multiple, as the complexity increases etc etc.

As for the lights thing, I've only tinkered with lights a bit but it seems lights can render incorrectly sometimes when it comes to different modes overlapping. Seems like something that might be an issue if someone has lighting in their game (but I haven't tried it specifically).

@TheDuriel
Copy link

TheDuriel commented Mar 25, 2022

How is this going to work without a shader anyways?

Clipping the mesh would be incredibly inefficent and hard to work with.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

@TheDuriel

You seem to have glanced at the headline of the issue and jump to write your comments without ever reading it properly.

An example for a masking shader is, iirc, given in the docs somewhere. If not, well its only two actual lines of code.

First of, no, such a shader is nowhere. Not in the docs, not anywhere else in the Godot space.
I can write a basic masking shader like this since 2 years:

void fragment() {
	vec4 mask_tex = texture(mask, UV );
	vec4 orig_tex = texture(TEXTURE, UV);
	float alpha = clamp(orig_tex.a - mask_tex.a, 0.0, 1.0);
	COLOR.a = alpha;
	COLOR.rgb = orig_tex.rgb;
}

But this is again not what this issue is about. You can't freely drag this mask texture of such a shader around. You can't see, scale it, manipulate it like a Sprite without prior writing all the shader code necessary for these operations. You can't simply add a child and hit a check boy and it will mask the parent with such a simple shader.

I'm sure there is a way to get the texture and the global position of a child and then load the texture into the sampler2D uniform of the parent shader and then somehow (I don't know how), transform the global position of the child to local UV space, ... but ... this is the opposite of intuitive, easy, straight forward or discoverable. It's an hardcore expert user solution for something you don't want to do that often.

We need a masking solution which is as simple and easy to use as a Sprite.

Also you seem to have a strange idea on how Light2D masking works. I suggest you test it.

@golddotasksquestions
Copy link
Author

@insomniacUNDERSCORElemon Light2D solution is still the most practical for very basic masks, but has a lot of issues.

it seems lights can render incorrectly sometimes when it comes to different modes overlapping. Seems like something that might be an issue if someone has lighting in their game (but I haven't tried it specifically).

Yeah this is an issue. For example you can't use 2 Light2D in mask mode, masking the same CanvasItem.

@TheDuriel
Copy link

shader_type canvas_item;

uniform sampler2D alpha_texture;

void fragment() {
	COLOR.rgb = texture(TEXTURE, UV).rgb; # Or COLOR.rgb = COLOR.rgb, for control nodes.
	COLOR.a = texture(alpha_texture, UV).r;
}

Please do not correct me with incorrect code.

If you want to add an offset, you can add another single uniform.

uniform vec2 offset;

And add this offset to the UV value. You can even multiply it by the pixel resolution if you want it in pixels.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

You can even multiply it by the pixel resolution if you want it in pixels.

Would you mind adding this in your sample code? I never managed to get global positional values to work when I offset my UVs.

@TheDuriel
Copy link

I'm sure there is a way to get the texture and the global position of a child and then load the texture into the sampler2D uniform of the parent shader and then somehow (I don't know how)

Iirc? Proposals are supposed to know how. Suggestions do not. Don't quote me on that.

But I can't see a way to do that. More so, how does that interact with shaders? Do I lose the ability to shade my sprite because your mask sets its own shader? There is no multi pass shading in 2D, and that would be slow.

@TheDuriel
Copy link

TheDuriel commented Mar 25, 2022

shader_type canvas_item;

uniform sampler2D alpha_texture;
uniform vec2 offset;

void fragment() {
        # Optional, multiply offset by SCREEN_PIXEL_SIZE.
	COLOR.rgb = texture(TEXTURE, UV + offset).rgb; # Or COLOR.rgb = COLOR.rgb, for control nodes.
	COLOR.a = texture(alpha_texture, UV + offset).r;
}

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

I've tried this many times. Multiplying the offset by SCREEN_PIXEL_SIZE alone won't work. No idea what other things you have to do (please share if you know) to have the mask show up at get_global_mouse_position() for instance.

I gave up on this many times. I will try again many times until I do figure it out. But that's my whole point here. This is not an intuitive user friendly or easy to use solution anyone can do. Masking is a basic task any beginner will want to do.

It should be straight forward as adding a Sprite. Not as cumbersome or difficult or complicated as it is now.

@TheDuriel
Copy link

TheDuriel commented Mar 25, 2022

to have the mask show up at get_global_mouse_position() for instance.

You are now talking about specific use cases, rather than generics. At that point I can't think but point to "If this enhancement will not be used often, can it be worked around with a few lines of script?"

It can be, absolutely. Doing space conversions is a fundamental skill you need to master to make games.

Global mouse position > Local sprite position > Feed that to the shader. Convert it to a UV offset. Which is a single multiplication with one of the (I do not recall which.) uniforms provided to you by Godot already.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 25, 2022

get_global_mouse_position() is of course just a placeholder example for ANY global position I would want the mask to set to.

Telling me something is "a fundamental skill you need to master" is really great. I've only been trying to master this fundamental skill for 3+ years. Like I said I know I have to somehow transform the global coordinates in to the local UV space, but I don't know how. I would still appreciate if you would teach me a lesson on something you consider fundamental or basic. If you don't want to that's fine. But something as top level as "Global mouse position > Local sprite position > Feed that to the shader" apparently does not help me. If you have a code sample that works, again I would really appreciate to try it. I have tried again today for a few hours but can't get it to work (have the mask at the global mouse position for example)

The fact that I've been trying to do this for this long despite learning heaps and bounds about all kinds of fundamental programming skills, alone says a lot about how there needs to be a better solution that works intuitively and easily for everyone imho.

@markdibarry
Copy link

To the point of this proposal, I have been working with Godot for years now as well, and this has been a feature that would be greatly appreciated. As someone who spent a good solid two months last year trying to recreate a feature similar to this in Godot, working with masking in 2D is poorly documented, buggy, unintuitive, and difficult. This is a common problem, and while "git gud" is A solution, I'd love for an easy to use built in solution.

@insomniacUNDERSCORElemon
Copy link

insomniacUNDERSCORElemon commented Mar 26, 2022

To further my previous comment... if it were viable, replace the checkbox with options like this:

Clip mode:

  • None (default)
  • Clip children: same as the current checkbox
  • Subtract/cut from parent: works in reverse (the issue here), and before parent clipping (only reason to do after would be replacing the drawn area, conversion effect on parent only)
  • Intersect children(/parent?): Like normal clipping except parent loses pixels that aren't shared by both sets of nodes. This one's a maybe, I added it because It'd allow you to get something like the example clipping image without it depending on color. Not sure how useful it'd be, though.

Clip receive:

  • Allow (default): same as all nodes now
  • Clipped by parent: clipped even if parent doesn't have clipping enabled. Useful if most things aren't clipped (thus parent doesn't have clipping enabled).
  • Unclippable by parent: opposite of option above.

In the improper-fix version (ghosting removed) node2d does have its children clipped by its parent so would be useful for grouping nodes based on their clip options, meaning an unclippable-by-children option isn't needed. You could add that option but you'd probably also want an unclippable (by all) option.

All of this would make in-engine art/characters more viable/powerful/dynamic, but subtract mode on its own would be a powerful tool on-par with the concept of clipping itself.


EDIT: @fire

As far as I know this is implemented by CanvasGroups.

I just tested canvasgroup with a subtract blend-mode material... it works with a transparent polygon. All colors delete all overlapped pixels but the only one that doesn't show up outside of the existing pixels (an intersection) is transparent. Might work if it used masking colors here but it doesn't, unless I'm missing something (underbaked or bug)? Seems like it's completely useless for sprites (unless you want a quadrilateral).

Possibly useful for polygons, but since canvasgroups don't seem to work with clipping (due to similar methods) that makes it useless in most cases there too.

Well, I should say that if you want to use a polygon to subtract from a sprite that will work.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Mar 26, 2022

Since I've heared now a couple people suggest Light2D or CanvasGroups instead: Light2D does not seem to have a mask mode in Godot4 any more. Like I already explained in the original post, it's also super weird to use in Godot3 already due to the render layer system and the inverted textures and the inability to use multiple Light2D masks together. Also who would look for a "light" node if they would want to "mask" a 2D element in their game?

CanvasGroups have a a lot of the same usability issues are Light2D nodes have in Godot3 for masking:

It's not easily discoverable. "CanvasGroup" says nothing about masking

It's an expert solution: You already need a high understanding of Godots systems to even get the idea that something like this might work and how it might work. Someone needs to tell you the solution, and since this is not very intuitive, it will be hard to remember if you don't do it ever day. This is what the CanvasGroup solution for masking looks like in the Scene Tree:
image
You then have to set up a CanvasItemMaterial for the mask and either use "substract" or "multiply" mode as contrary to the default mix mode. This is hard to discover even when you know what to do. Impossible to figure it out intuitively!

This again has the same issues as the current Light2D implementation:

The mask works in reverse logic, even worse than the Light2D: The Sprite you assign the "Substract" CanvasItem Material to, is not the one that does the substracting. Which all by itself is confusing as hell.
Example:
G4_A5_canvasitem_substract01
(here the white circle has the substact material, but the Godot icon without material aka the default mix is doing the substraction)

It's the same for a black-and-white mask btw as you can see here:
G4_A5_canvasitem_substract02

If set the CanvasItem Material of the mask to "multiply", the correct node finally does the masking, but just like the crazy current Light2D implementation you will have to use an image with transparency and it's the transparency that does the stancing, not the part of the texture without alpha. Which again is super counter-intuitive. This is what this looks like:
G4_A5_canvasitem_multiply01

If you try to use a black-and-white texture, it of course won't work at all:
G4_A5_canvasitem_multiply02

Even if these issues would be fixed and or documented, it will never be able to compete with the intuitiveness of simply adding and positioning a Mask2D child node, or enabling a mask_parent property of a Sprite.

@SilencedPerson
Copy link

SilencedPerson commented Mar 26, 2022

@markdibarry

To the point of this proposal, I have been working with Godot for years now as well, and this has been a feature that would be greatly appreciated. As someone who spent a good solid two months last year trying to recreate a feature similar to this in Godot, working with masking in 2D is poorly documented, buggy, unintuitive, and difficult. This is a common problem, and while "git gud" is A solution, I'd love for an easy to use built in solution.

I agree with your point there, Godot is often presented as easy to use and beginner-friendly, this image starts with the chummy logo, the node system and ends with GDScript's ease of use, it's quite frankly it's one of it's main selling points over engines like Unity and Unreal. Having something used so frequently in 2D games be only truly solvable by shaders is counter-productive to that image. I have small business teaching teens how to make games and it often comes up as a question, for example, just few days ago, one of my students who is making small horror game, needed to make simple textured fog, we tried the proposed approach of using Light2D and we indeed ran into several problems that the @golddotasksquestions correctly identifies.

It really is not covered by Light2D and it is not covered by CanvasGroup either. The user experience is subpar and requires user to reach for somewhat low level solution like shaders (often very daunting for beginners) when this is something so general and used in many different kinds of games. Before Godot, i used to teach Game Maker and while Godot made the overall process and learning curve easier some things are still not exactly frictionless and i see this need for Mask2D node as a symptom of different issue that are Godot's limited custom drawing capabilities.

This proposal can easily coexist with CanvasGroup and actually be very useful in tandem.

@dugramen
Copy link

dugramen commented Mar 26, 2022

I think a mask property is a bit too cluttered. A Mask2D node sounds good, but the name should be more specific as it sounds like all your masking purposes would be found there, whereas it's only for cutting the parent. Maybe ClipMask2D or something

And in case this doesn't get added, it's currently possible to do this with Canvas Group in a more reliable way than Light2D and more friendly way than a shader. A canvas group with 2 children, the pear and the bite mask. The bite mask has CanvasItemMaterial set to multiply, and the bite texture should be transparent where you want the mask to cut (inverted in the OP's case). (subtract_mode is broken, but it would make this better by not having to invert it)

@SilencedPerson
Copy link

SilencedPerson commented Mar 26, 2022

I think a mask property is a bit too cluttered. A Mask2D node sounds good, but the name should be more specific as it sounds like all your masking purposes would be found there, whereas it's only for cutting the parent. Maybe ClipMask2D or something

And in case this doesn't get added, it's currently possible to do this with Canvas Group in a more reliable way than Light2D and more friendly way than a shader. A canvas group with 2 children, the pear and the bite mask. The bite mask has CanvasItemMaterial set to multiply, and the bite texture should be transparent where you want the mask to cut (inverted in the OP's case). (subtract_mode is broken, but it would make this better by not having to invert it)

What you describe as possible solution was several times highlighted as a problem in this thread. CanvasGroup is not viable.

@atngames
Copy link

Hi, I am really interested to follow this proposal.
ClipMask2D is a better name for it, unless it can perform all sorts of masking operations (which would be awesome).
Atn

@Lielay9
Copy link

Lielay9 commented Mar 26, 2022

I'd simply like to remind you that in most cases while moving the mask might be more intuitive as a child, the effect is often desired the other way down the scene tree. Look at the following picture for example:

image

Let's say the previous represents my player scene. If I'd want to mask it during gameplay I'd have to go inside the player scene and manually add a mask under each node it has. In this case, the proposal most certainly is not THE fix, as it requires a very specific tree structure. It may even be detrimental for beginners who are led to believe that this is THE solution and who waste time littering their scene with mask nodes. I still think that a mask node might work, but to do that, it must work down the tree and not change the position of its children. As of now, this would only be feasible with CanvasGroups. The least intrusive way would be to document how to do it, and the second-least intrusive would be to introduce the Mask2D as a node specifically to be used with CanvasGroups.

No matter what, I'm more interested in pursuing a solution that works most of the time, preferably all of the time, rather than a case-specific one, which I deem this one to be. That said, with minor changes to the proposal I could see it working and perhaps even being helpful for beginners. However, I am not certain if it would be used often enough to be warranted as a core feature and if it truly is too difficult to implement with the help of proper documentation or as a plugin.

@SilencedPerson
Copy link

SilencedPerson commented Mar 26, 2022

@Lielay9 in this case, you have to combine it with CanvasGroup that would encapsulate everything and Mask2D as it's child.

@shafnaz
Copy link

shafnaz commented Sep 7, 2023

We shouldn't give up, look how long this thread has become. Godot has the potential to become one of the leading tools. We should turn it into an addon.

@nathanfranke
Copy link
Contributor

nathanfranke commented Sep 16, 2023

Now that clip_children is working in the latest Godot, can the exact feature / behavior being proposed be clearly defined again?

From what I see clip_children works perfect for alpha masking (there is still an issue with OpenGL instead of Vulkan godotengine/godot#74186).

OP:

enabling clip_children does not solve the need for simple straight forward intuitive masking and positioning it in an equally simple and straight forward way.


Edit: I got my use case working, mixing and masking, multiply blend mode.

blend.2023-09-17.13-49-39.mp4

Godot 4.2.dev4 test3.zip

If the top layer doesn't have any alpha, you can skip the shader and just use canvas item material (still a shader internally).

When I export a transparent PNG from gimp, semi-transparent areas are still very colored, lerping rgb to black with alpha fixed it for me. Thanks Zylann - https://ask.godotengine.org/11867/achieve-multiply-blending-with-alpha-channel-transparency

It might not be perfect, (0.5,0,0) * (0.5,0,0) = (0.25,0,0) which might be too dark. But there's only like 5 blend modes in Godot.

@golddotasksquestions
Copy link
Author

@nathanfranke

It seems to me like you did not read the original post or any of the comments that followed:

The issue is not that this effect was or is impossible to achieve, this proposal is about massively simplifying how to achieve it!
I'm (and a lot of other people apparently) asking for a straight forward intuitive approach, just as simple as adding a Mask2D node or enabeling a "mask parent" checkbox on a Sprite2D.

I'm telling you absolutely noone is going to intuitively figure out by themself they have to use a CanvasGroup, and a CanvasItem material on Substact mode when they are looking for a simple way to mask a CanvasItem. CanvasGroups are all great, but this approach you are proposing as solution sucks in terms of user friendliness (also, there is still the issue with premultiplied alpha ...)

@MrFaul
Copy link

MrFaul commented Sep 27, 2023

This is basic functionality I don't get why this isn't a thing already.
Heck even HTML + CSS can do that. (Image blend-modes are quite powerful)

I recently started to play around with Godot and I'm trying to blend dynamically between layers of a tilemap.
I just want a mask that I can pin to any node, target a specific object in this case the "top layer" and be done with it.

As it stands now I probably need a shader for it which I find quite baffling and off putting.

@golddotasksquestions made a complete sane request, I don't understand the drama around it.

@Dorifor
Copy link

Dorifor commented Nov 5, 2023

Did someone try to implement this already ? It's a basic feature that could really enhance workflows.

@TushieSushies
Copy link

Not only enhance, in my case it’s impossible to implement a feature because lights don’t have masks anymore (for some unknown reason)

@MrFaul
Copy link

MrFaul commented Nov 7, 2023

@TushieSushies Naa, a custom shader or the CanvasGroup/CanvasItem Material combo should do nicely.

I got away without the need for a shader, but it is still cumbersome and obscure.

This isn't something that's solvable without a considerable amount of time investment into researching the topic.

For such basic feature, this should be viewed as unacceptable.
So yeah, to reiterate, I really don't get the drama around it...

But I have to admit this is a QOL feature so it's understandable if this on the back burner.
But from the looks of it, it's not even considered because there is a "workaround" because you can do that with feature "X"
But that's completely missing the point.

All a lot of people want is that this gets on the road map / planned features.
So please humor us.

As a casual user of Godot mainly to churn out some tools to improve workflow for various things and some prototyping of ideas as a hobby I can't justify the time that would be required to look into the codebase my self to make a pull request that implements this.
I'm an engineer not a software developer.
So please carry this topic to someone who is familiar with the inner workings of Godot and shouldn't have difficulties to implement that.

It really is something essential and not something redundant.

@vyshliy
Copy link

vyshliy commented Nov 7, 2023

Masking parent would be a good compromise and would close many use cases until a more universal method is proposed.

By the way, Light2D in mask mode was quite flexible to use. Supported layers and it was independent of the parent's position. Why was it abandoned at all? It was worth adding a simple mask mode to it, which works without adding material to masked sprite.

Adding a node based on that Light2D that only does mask is a matter of a couple of lines of code. Addon would appear in the library soon if the duplication of such functions is too cumbersome for the core.

Anyway, masking in 2D graphics is one of the main functions. Lighting itself and tile editors are great, of course, but these are far more specific things.

@SkwittApophis
Copy link

SkwittApophis commented Nov 8, 2023

The issue I have with the CanvasGroup/CanvasItem solution is that while Light2D is a canvas item, the light/shadow rendered part isn't affected by the CanvasGroup so it can't be used as a mask. I also don't know how to access that component of the light from a shader to use it that way.

clip_children does not clip based on the Light2D so you can't make clipping based on something that is also occluded by light occluders.

@IDDQD69
Copy link

IDDQD69 commented Dec 3, 2023

The issue I have with the CanvasGroup/CanvasItem solution is that while Light2D is a canvas item, the light/shadow rendered part isn't affected by the CanvasGroup so it can't be used as a mask. I also don't know how to access that component of the light from a shader to use it that way.

clip_children does not clip based on the Light2D so you can't make clipping based on something that is also occluded by light occluders.

I've been bashing my head agains the wall for this today. This is for me a HUGE regression from godot 3.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Dec 4, 2023

@IDDQD69 @SkwittApophis

Would this proposal here solve your issue? If not, please open a new proposal. You can edit your comments to link to it.

@vyshliy
Copy link

vyshliy commented Dec 22, 2023

Looks like that closes this particular proposal. I still have a question about the dependence of the mask on the position of the parent. You don't have to create this dependency at all.

Implementation proposal:

  • Any CanvasItem has a "use as mask" bool button
  • Light Mask is used as a choice of mask layer when "use as mask" is on.

Thus, the position of the mask, is independent of the position of the parent, is configured very simply, can be simultaneously applied to several nodes, and one node can be under the influence of several masks. Since light cannot be applied to a mask, using Light Mask layers should not cause problems. Changes in the editor are minimal: "clip_children" will be replaced with “use_as_mask” bool button.

I think I’ll put this into a separate proposal later, but first I’ll publish it here.

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Dec 22, 2023

@ vyshliy

Looks like godotengine/godot#74859 (comment) closes this particular proposal.

Unfortunately it does not close this issue at all. What is in this pull request is still all parent -> child masking like we already have in various forms. It's not child -> parent masking or parent <-> child independent masking (like we have in Godot 3.X with Light2D node using Render Layers).

I still have a question about the dependence of the mask on the position of the parent. You don't have to create this dependency at all.

I agree. Light2D node masking in Godot 3.X we at least had completely parent-child relationship independent masking. However this is also a rather advanced technique for masking, not as intuitively and immediately understood as a simple Mask2D node.

I totally agree we need to have the functionality of the Light2D node back somehow, but I did not cover immediate/advanced usecases in this proposal.

Any CanvasItem has a "use as mask" bool button
Light Mask is used as a choice of mask layer when "use as mask" is on.

This sounds great to me as well. I still would like to have a Mask2D node, as I believe what you are proposing here is another advanced masking technique (one we had with the Light2D node definitely need back), and what we lack here is a simple easily discoverable, and intuitive to use method.

I think I’ll put this into a separate proposal later, but first I’ll publish it here.

Yes, please link these proposals so people can easily find it!

@vyshliy
Copy link

vyshliy commented Dec 22, 2023

Unfortunately it does not close this issue at all. What is in this pull request is still all parent -> child masking like we already have in various forms. It's not child -> parent masking or parent <-> child independent masking (like we have in Godot 3.X with Light2D node using Render Layers).

You are right. I tested the changes and it definitely doesn't solve the overall problem with masks. For some reason, this option is controlled by the parent and all children of the parent node with subtract mode are now masks, which is definitely overkill - you just can't put visible children in that node anymore. This, of course, solves the problem of nested masking that clip_children had, but the method is definitely far from convenient.

While I agree with you about the simplicity of the approach with a separate node (Mask2D), in your proposal I do not see a solution to the problem of dependence on the parent in it. Using any canvasItem as a mask can be very flexible and at the same time simple, because it will be just one click “use as mask”. I will formalize the proposal and provide a link to it here a little later.

@golddotasksquestions
Copy link
Author

Using any canvasItem as a mask can be very flexible and at the same time simple, because it will be just one click “use as mask”. I will formalize the proposal and provide a link to it here a little later.

Yes please do and link it here in your comment so it's easier to find!

@vyshliy
Copy link

vyshliy commented Dec 22, 2023

I thought about it and your separate node approach really looks the most convenient in terms of usability. Here is my basic vision of implementation.

Mask2D has two main parameters:

  • Texture
  • Masking layer

The masked node is determined by the layer, as it was in Light2D. This way Mask2D can be positioned freely on the scene and does not depend on the parent's position and setup. One Mask2D can affect several masked nodes at once, just as several masks can affect one masked node.

Opaque pixels of texture are used as a mask, as is currently implemented in clip_children.

Any СanvasItem is affected by a Mask2D if its light mask layer corresponds to the Mask2D's layer. The use of special material is not required.

Mask2D

@golddotasksquestions
Copy link
Author

golddotasksquestions commented Dec 23, 2023

It would be nice if by default Mask2D would just work, even for stupid people like me. Meaning you can add it to any CanvasItem node, be it Control or Node2D type, as child, give it a texture and it would mask the parent with it's opaque pixels.

If the Mask2D should also have settings for the 2D Render Layers, I think we would also need to have a checkmark button to toggle "mask only parent" property on and off. The default state should be to only mask the parent to make it as least confusing as possible for new users to get into 2D masking.

image

If that's not feasible to implement, I would much rather suggest to keep the Mask2D node simple without the option to target specific Render Layers, and instead consider to add this functionality back into the Light2D node (now called PointLight2D in Godot 4) where it originally was.

@JekSun97
Copy link

We can only hope that the developers will hear us and add such functionality without addons.

@Kryyto
Copy link

Kryyto commented Mar 9, 2024

This feature would be greatly appreciated.

@inhalt120g
Copy link

this proposal is about massively simplifying how to achieve it! I'm (and a lot of other people apparently) asking for a straight forward intuitive approach, just as simple as adding a Mask2D node or enabeling a "mask parent" checkbox on a Sprite2D.

I can't see how is adding a masking object and then having to tick "mask parent" intuitive. Out of curiosity, have you ever used or seen Photoshop, Illustrator or InDesign (or if we want to get out of "industry standard" software: anything like Procreate, ProMotion, Aseprite… or any other 2d art program that uses masking)?

@bhottovy
Copy link

bhottovy commented Mar 11, 2024

this proposal is about massively simplifying how to achieve it! I'm (and a lot of other people apparently) asking for a straight forward intuitive approach, just as simple as adding a Mask2D node or enabeling a "mask parent" checkbox on a Sprite2D.

I can't see how is adding a masking object and then having to tick "mask parent" intuitive. Out of curiosity, have you ever used or seen Photoshop, Illustrator or InDesign (or if we want to get out of "industry standard" software: anything like Procreate, ProMotion, Aseprite… or any other 2d art program that uses masking)?

Add a masking object "or" mask parent option. Also you are comparing a game engine with image editing/animation software...

@inhalt120g
Copy link

inhalt120g commented Mar 11, 2024

Add a masking object "or" mask parent option.

You are just repeating what has already been said and replying in a kind of a non sequitur fashion (me: "I don't see how is using this particular method of solving this problem intuitive", you: "use this particular method").

Also you are comparing a game engine with image editing/animation software...

Where did I compare them? I only asked about the poster's experience with art packages in order to see if I should bring attention to what is generally considered a convenient (and intuitive etc) way of masking graphics.

Masking is not exclusive to game engines (in the same way, I'd bring Photoshop up if we talked about what kind of colors I expect to get from a particular blending mode etc in Godot, it does not imply Photoshop and Godot have that many similarities) and there is nothing wrong with being informed of how is masking already solved elsewhere.
(Edit: fixed formating)

@Necronomicron
Copy link

I have non-static objects (let's call them type A), other objects (type B) and a background, all in 2D. Type B objects are semitransparent and are partially over type A objects. I want to mask/cull type A objects so their parts that are under type B objects would be invisible, so only the background would be visible behind type B objects. All objects are scattered across the scene tree and aren't children of each other, so I guess in my case it would be better to have something like layers, similar to collision layers or whatever.

@MelPeslier

This comment was marked as off-topic.

@AThousandShips
Copy link
Member

@MelPeslier This is not the place to ask support questions, please read the documentation or ask in the forums

@MrFaul
Copy link

MrFaul commented Jun 3, 2024

? It isn't, it is still open.

And yes this is basic functionality.
There are workarounds to achieve that, many posted here, but it is still something obscure and not easily done by a novice who just wants a node and be done with it.

Details matter here, and it would be nice if newbies wouldn't have to deal with that since it requires at minimum a basic understanding of the rendering pipeline.
That's a bit much to ask since even a basic understanding is already quite complex.

But since it's a QOL feature it's on the back burner.
It probably will arrive someday but who knows when.

@AThousandShips
Copy link
Member

But since it's a QOL feature it's on the back burner.

Not really, it's waiting for someone to go ahead and implement it, it'll be available whenever someone is willing and interested in implementing it

@MrFaul
Copy link

MrFaul commented Jun 3, 2024 via email

@AThousandShips
Copy link
Member

My bad it sounded like you said it was de-prioritized, by that logic everything is on the back burner 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Discussion
Development

Successfully merging a pull request may close this issue.