Draw over another sprite as a mask?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By dugtrioramen

I basically want to draw things (colors, textures, shapes) over another sprite or shape, but nothing outside the sprite would be drawn. Like a blood on walls effect, or a sprite being covered in paint.
I don’t know shader stuff at all, and from what I read I might need to use viewports or canvasitems, but I don’t know much about those either.
I am starting to learn godot, coming from gamemaker. In gamemaker you could create a surface, disable the color channels so only the alpha values are being drawn, draw the sprite you want to be drawn over, enable the colors again but disable the alpha channel, then draw the mask.
Is there anything similar or an equivalent surface/color channels control in godot? If not, how would i achieve this?

Thank you for any help!

I have a similar problem. If you can take an image file as your mask, you can use a shader like this one:

shader_type canvas_item;
uniform sampler2D mask;
void fragment() {
    vec4 color = texture(TEXTURE, UV);
    vec4 vmask = texture(mask, UV);
    color.a = vmask.a;
    COLOR = color;
}

And paste it into a .shader file.
You first have to create a ShaderMaterial for the object you want to mask, then drag the shader file in the shader parameter, then click on the shader param, and finally drag the image into the mask parameter.

You can do the same thing in code by writing

$my_node.material = ShaderMaterial.new()
$my_node.material.shader = preload("res://mask_shader.shader")
$my_node.material.set_shader_param("mask", preload("res://my_mask.png"))

Now, if you need to use drawn shapes as your mask, then I’m just as stuck as you are. You have to turn your drawn shapes into a texture first, and I have yet to find a viable solution to that…

Good luck to you, hope this helps :slight_smile:

MickaelH | 2019-12-26 12:19

Thanks a lot for the shader! Although the mask and the sprite seem to be swapped for my case, it worked really well.
I looked into shaders a bit after seeing this, and i think changing:

color.a = vmask.a

to:

color.rgb = vmask.rgb

does the trick for me.
But yeah, as you said, I was kind of hoping to be able to use polygons as well.
I was wondering if there was a way to draw polygons in shaders. Then maybe you could just export (is it uniform?) the points and use gdscript on it.

dugtrioramen | 2019-12-26 17:06

I found a way, but it’s extremely convoluted.

  1. add a ViewportContainer to your scene and put it off screen so it doesn’t appear in your game.
  2. add a Viewport inside that ViewportContainer, and a CanvasItem inside that Viewport.
  3. add a script to your CanvasItem, and create your _draw function in there.
  4. in the _ready function, add this code:
    get_viewport().set_clear_mode(Viewport.CLEAR_MODE_ONLY_NEXT_FRAME)
    yield(get_tree(), "idle_frame")
    yield(get_tree(), "idle_frame")
    var img = get_viewport().get_texture().get_data()
    img.flip_y()
    var texture := ImageTexture.new()
    texture.create_from_image(img)
    emit_signal("texture_generated", texture)
  1. on your main scene, connect your CanvasItem to listen to the “texture_generated” signal in order to retrieve the texture and use it.

Note that this whole process takes 2 frames because you have to wait for the Viewport to be refreshed after drawing your shapes in order to export it into a texture.

MickaelH | 2019-12-26 17:22

Oh i see. If i understand it correctly, you just draw the shape offscreen inside a separate viewport, then convert that entire viewport into a texture, and then use that texture as the mask in the shader.
I don’t think I can use this though as you have to load it offscreen, and I wanted to have a couple of these shape masks. It seems like the only way to do complex drawing, but I think i can do with drawing simple quads in the shader.
But Thank you so much for your help!

dugtrioramen | 2019-12-26 19:09