-
-
Notifications
You must be signed in to change notification settings - Fork 97
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
Add a "Mask2D" node (and/or a "mask_parent" property for Sprite2D) #4282
Comments
This can surely be made more convenient by an add-on which provides its own editor gizmos for placing masks. |
@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. 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: But an having this basic 2D functionality as addon makes no sense whatsoever. |
As far as I know this is implemented by CanvasGroups. https://godotengine.org/article/godots-2d-engine-gets-several-improvements-upcoming-40 |
@fire I know about CanvasGroup. How is it equal to the Mask2D I am proposing here? |
I repeat it here: The point of this proposal is to make 2D masking as easy as adding and positioning a Sprite node. |
This is already implemented in Light2D. |
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) |
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. |
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). |
How is this going to work without a shader anyways? Clipping the mesh would be incredibly inefficent and hard to work with. |
You seem to have glanced at the headline of the issue and jump to write your comments without ever reading it properly.
First of, no, such a shader is nowhere. Not in the docs, not anywhere else in the Godot space.
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. |
@insomniacUNDERSCORElemon Light2D solution is still the most practical for very basic masks, but has a lot of issues.
Yeah this is an issue. For example you can't use 2 Light2D in mask mode, masking the same CanvasItem. |
Please do not correct me with incorrect code. If you want to add an offset, you can add another single uniform.
And add this offset to the UV value. 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. |
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. |
|
I've tried this many times. Multiplying the offset by 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. |
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. |
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. |
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. |
To further my previous comment... if it were viable, replace the checkbox with options like this: Clip mode:
Clip receive:
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
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. |
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 It really is not covered by This proposal can easily coexist with |
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. |
Hi, I am really interested to follow this proposal. |
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: 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. |
@Lielay9 in this case, you have to combine it with |
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. |
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. |
Would this proposal here solve your issue? If not, please open a new proposal. You can edit your comments to link to it. |
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:
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. |
@ vyshliy
Unfortunately it does not close this issue at all. What is in this pull request is still 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.
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.
Yes, please link these proposals so people can easily find it! |
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. |
Yes please do and link it here in your comment so it's easier to find! |
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:
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. |
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. 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. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
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... |
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").
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. |
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. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@MelPeslier This is not the place to ask support questions, please read the documentation or ask in the forums |
? It isn't, it is still open. And yes this is basic functionality. 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. 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 |
That's exactly what I would call back burner 😄
Godot is FOSS after all.
|
My bad it sounded like you said it was de-prioritized, by that logic everything is on the back burner 😆 |
Flash had masking exactly like in the proposal. Also, in Flash masking disabled pixels in a layer so to speak (in Sprite regardless of how deep child Sprites chain was). I remember blending was available on Sprite but not for masking. I'd say Flash is closer to Godot in 2D than Illustrator and Photoshop (I mean you could animate a mask changing position, programatically too). And I can vouch for its ease of use, completeness and top notch docs. Currently just to mask an icon based on the shape of a button (to copy its rounded corners for example) would take me hours to figure out Canvas Group as described above. I think. I'll get to it. |
You mean, you actually want to make this topic a reality? |
This is indeed the intended usage for the CanvasItem clipping functionality, where the parent node defines the mask for the children. But please note that the discussion is not about this, but about another usecase wich is "carving holes out of sprites" preferably using child nodes of the masked sprite, wich can be done in godot 4 using custom shaders or messing with the CanvasItemMaterial. But none of these approaches is as easy as it was in godot 3 with the mask mode of light2d. |
It would be also great to have in Sprite3D, but if not then I guess that it could also be done with a subviewport. |
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 VisualServercanvas_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:
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Usage would be intuitive, straightforward and dead-simple:
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Edit2: Multiple Mask2Ds:
mask2d_2.mp4
Edit3: difference between the existing
clip_children
and Mask2D /mask_parent
property of this proposal: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.
The text was updated successfully, but these errors were encountered: