0 votes

In my project I need to mask a sprite hierarchy in order to clip it to a region of a certain shape so that whatever is outside of that shape won't be visible.

To be more precise, I'm trying to make an eye of a character with moving parts that the player could choose the color of the iris (I'm modulating a grayscale image of the iris with a color) and then the iris would be able to move inside the eyehole and remain clipped to its shape instead of sticking out.

How can I use an image of the eyehole shape to clip the sprite hierarchy of the eyeball?

I tried the approach with light map described here, and it seems to work fine:

Light mask for one eye

but only as long as there's only one eye on the screen :P As soon as I introduce another one and make them overlap, their light maps start to interfere in a weird way as shown here:

Light mask derps

as if the masks were XORing with each other or something (the invisible region of one light mask makes the other mask's invisible region visible again, and only where they overlap, they mask each other correctly again). Which obviously is not good, because I need to have more than one eye on the screen (there may be more than one character visible at any time).

Is it some kind of a bug? Or am I doing something wrong?
How to make it work right?
Or maybe there is some better way to do it than with light masks? E.g. some texture with alpha channel or something? (Except that for an entire hierarchy of sprites instead of just a single one.)

Edit: I tried to fix it by adding some transparent pixels around the masking shape and it helped a little bit: at least now the eyes don't interfere that often because their masks overlap most of the time.
But the problem is far from solved: When I added the eye as a part of another hierarchy of sprites (a head), the masks now interfere with the head too! :o

Overlap with the head

Even if the head (green) is clearly in front of the eye on the left, and normally occludes it, the area of the right eye that was previously hidden by the mask (the white rectangle for the white background of the eye) starts to be visible now through the area of the left eye's mask even if it's behind the head and shouldn't be even visible!
It seems that the light masks don't care about the Z order of the scene at all and if they overlap with some pixels, they make them visible no matter what :P

Here's the project file if you want to play with it and see the derps in action:
http://mistu.info/Stuff/Godot/EyeDerp/EyeDerps.zip   (EyeTest.tscn)

asked Dec 1, 2017 in Engine by SasQ (62 points)
edited Dec 1, 2017 by SasQ

1 Answer

+1 vote

Instead of using sprites, maybe you could use a Polygon2D for the eye? You could move the iris by offsetting the UV's in the fragment shader:

uniform vec2 uv_offset = vec2(0, 0);
COLOR = tex(TEXTURE, UV + uv_offset);

You could also do the coloring of the iris in the shader.

answered Dec 1, 2017 by mollusca (1,524 points)

On second thought the Polygon2D is a bit unnecessary, you can combine the iris- and eye-textures in a shader on a Sprite:

uniform vec2 iris_offset = vec2(0, 0);
uniform texture iris_tex;
uniform color iris_custom_col;

vec4 iris_col = tex(iris_tex, UV + iris_offset) * iris_custom_col;
vec4 eye_col = tex(TEXTURE, UV);

COLOR = vec4(mix(eye_col.rgb, iris_col.rgb, iris_col.a), eye_col.a);

The problem is, they are not just simple textures - they are sprite hierarchies. If it was about clipping a simple sprite image, I wouldn't be asking, I would simply use alpha transparency on that sprite. Instead, I need to clip a sprite hierarchy into a region defined by a single image with alpha mask. See the project attached.

At least the hierarchy in your test scene is entirely doable in a shader, basically you just add one more texture to the code above. A shader isn't as flexible or easy to use as a number of sprites but for this case it could work. I made a little demo, give it a look.

Still, your solution is based on mixing textures in the shader, not Sprite nodes or Sprite hierarchies. My question was about masking an actual hierarchy of nodes in a scene (their visible image) by an image mask, so that part of the hierarchy would become invisible. Keep in mind that a hierarchy can be much more complex than in my example, with animated subnodes, changing relative positions to each other etc. In that case, simple mixing of textures won't work.

I may use your solution with just texture mixing if all else fails, in this particular case (of an eye) it may work. But it won't work in general, with any custom hierarchy of nodes (e.g. GUI elements).

So I upvote your answer for clever idea, but I'll leave the question open until someone posts a proper solution.

Follow-up question regarding the shader and texture offsets:
When I flip the sprite horizontally either by the flip parameter or scale, the eyes derp :P
They move in the opposite direction than the mouse.
How can I move them towards the mouse regardless of the flip?

Edit: I tried to use get_scale() and check if its x component is negative, and flip the vector pointing from the sprite to the mouse if that's the case. It seems to work when I flip the sprite by scaling, but it cannot detect if some of its parents has been flipped instead :q

Edit 2: OK nevermind, I found the solution myself :) I changed this line of your code:

var mouse_vec = new_pos - get_global_mouse_pos()

to this:

var mouse_vec = -get_local_mouse_pos()

and it works like a charm :) It seems that what was needed was the local position of the mouse pointer relatively to the sprite's position in the world, with whatever transform it has applied. Because the same local transform is then used for the UV coordinates of the texture.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.