godotengine / godot

Godot Engine ā€“ Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.35k stars 21.06k forks source link

A way to erase pixels or overwrite alpha channel of pixels using the draw_line method() and a new blend mode #10255

Closed blurymind closed 4 years ago

blurymind commented 7 years ago

I am currently trying to create a tiny pixelart tool for godot, but it's not possible to use the draw_line() method for erasing pixels. There seems to be no way in replacing the alpha value of a pixel with another alpha value at the moment - in the way you can draw with another color on top of old lines.

As @Zylann said

@blurymind you could overwrite alpha pixels as if they were a normal color component, I believe there are blending modes in OpenGL to do that, but Godot doesn't expose them. This is a precision I added at the beginning of the thread, no need to repost the entire code :)

this use case concerns blending mode when rendering on texture, not on screen (which wouldn't make much sense because screen has no alpha)

I think there is a workaround but it's impractical, you would need to do your own blending with a shader by drawing transparent pixels as a red color on a separate texture, and then blends your image + the red texture together with a shader that erases pixels that come in contact with red pixels, to render it on the final render target which you can then save... but an appropriate control on blending mode would be much more simpler. Currently it is possible, but very impractical.

Here is an example code for drawing freehand lines:

extends Container

var _pen = null
var _prev_mouse_pos = Vector2()
var viewport = Viewport.new()

func _ready():
    set_process_input(true)
    # Make it so it renders on a texture
    viewport.set_as_render_target(true)
    # Set the size of it
    viewport.set_rect(get_parent().get_rect())
    viewport.set_transparent_background(true)
    # I tried to set the background color, but none of this worked...
    _pen = Node2D.new()
    viewport.add_child(_pen)
    _pen.connect("draw", self, "_on_draw")
    # Don't clear the frame when it gets updated so it will accumulate drawings
    viewport.set_render_target_clear_on_new_frame(false)
    add_child(viewport)

    # Use a sprite to display the result texture
    var rt = viewport.get_render_target_texture()
    var board = Sprite.new()
    board.set_texture(rt)
    board.set_centered(false)
    add_child(board)
    set_process(true)

func _process(delta):
    _pen.update()

var color = Color(1, 1, 0,1)
func _on_draw():
    var mouse_pos = _pen.get_global_mouse_pos()
    _pen.draw_line(mouse_pos, _prev_mouse_pos,color,5 )
    _prev_mouse_pos = mouse_pos

func _input(event):
    if event.is_action_pressed("eraser"):
        print('using eraser now')
        color = Color(VisualServer.get_default_clear_color())  #<-- this approach to setting the brush to be an eraser doesnt work!
    if event.is_action_pressed("save"):
        viewport.queue_screen_capture()
        yield(get_tree(), "idle_frame")
        yield(get_tree(), "idle_frame")
        yield(get_tree(), "idle_frame")
        var captured = viewport.get_screen_capture()
        captured.save_png("res://screenshot.png") #<-- capture and you will see why

color = Color(VisualServer.get_default_clear_color()) Will fail to erase the yellow pixels in the example.

Instead it simply writes the background color and you get this ugly grey color when you export the image with viewport.get_screen_capture(): screenshot

The ideal solution for me personally is if I could set the color value to (0,0,0,0) and have that act as an eraser. If we could have Godot actually have VisualServer.get_default_clear_color() be a color that erases pixels whenever set_transparent_background is true- that would make more sense too. Or perhaps the best approach to this is to have a new blending mode that supports it.

I am assuming that something has to be done to the way godot's draw_line method works when writing to a texture. Any solution to this is very much welcome. Making it possible will allow people to implement a freehand eraser tool and possibly other very useful things using the draw_line method

Zylann commented 7 years ago

I would precise that this use case concerns blending mode when rendering on texture, not on screen (which wouldn't make much sense because screen has no alpha)

blurymind commented 7 years ago

@Zylann we can still screen capture with alpha though

The viewport has set_transparent_background ( true ) http://docs.godotengine.org/en/stable/classes/class_viewport.html?highlight=screenshot#class-viewport-queue-screen-capture

blurymind commented 7 years ago

has anyone figured out a way to erase pixels using the draw_line method?

Zylann commented 7 years ago

@blurymind it's about adding a new blend mode I think, that will use alpha like a regular color, to replace the alpha already in the target buffer. Maybe @reduz knows how to do it?

blurymind commented 7 years ago

@reduz is there a way to erase pixels or overwrite pixels using the draw_line method? I need it to implement a simple freehand eraser tool

Zylann commented 7 years ago

@Tvs-Frank you can't write the background color to "erase" pixels if your background is not a solid color (or worse, animated)

blurymind commented 7 years ago

I was wondering, is there a way to overwrite pixels? If so, we could over write the pixels with 1 for alpha with pixels with 0 alpha. How do you do that in godot?

Zylann commented 7 years ago

@blurymind you could overwrite alpha pixels as if they were a normal color component, I believe there are blending modes in OpenGL to do that, but Godot doesn't expose them. This is a precision I added at the beginning of the thread, no need to repost the entire code :)

I think there is a workaround but it's impractical, you would need to do your own blending with a shader by drawing transparent pixels as a red color on a separate texture, and then blends your image + the red texture together with a shader that erases pixels that come in contact with red pixels, to render it on the final render target which you can then save... but an appropriate control on blending mode would be much more simpler.

blurymind commented 7 years ago

Can we please tag this as a feature request or should I create another issue for a request?

On 31 Aug 2017 10:42, "Marc" notifications@github.com wrote:

@blurymind https://github.com/blurymind you could overwrite alpha pixels as if they were a normal color component, I believe there are blending modes in OpenGL to do that, but Godot doesn't expose them. This is a precision I added at the beginning of the thread, no need to repost the entire code :)

I think there is a workaround but it's impractical, you would need to do your own blending with a shader by drawing transparent pixels as a red color on a separate texture, and then blends your image + the red texture together with a shader that erases pixels that come in contact with red pixels, to render it on the final render target which you can then save... but an appropriate blending mode would be much more simpler.

ā€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/10255#issuecomment-326245277, or mute the thread https://github.com/notifications/unsubscribe-auth/AGMbVYssoQc0hQ7umU3SVdntrVf5ksvOks5sdoAYgaJpZM4O0ygy .

akien-mga commented 7 years ago

Sounds like a good feature request. If you can edit the original post for clarity based on the follow-up discussion, that would be great.

blurymind commented 7 years ago

@akien-mga Thank you :) I updated the title and the first post to more accurately reflect the request Please let me know if it needs more amendments

@Zylann another approach could be to have VisualServer.get_default_clear_color() be a color that erases pixels whenever set_transparent_background is true? Not sure what design would be most elegant here

Zylann commented 7 years ago

a color that erases pixels whenever set_transparent_background is true

A Color cannot hold such info beyond having an alpha of zero. What you mean is again a blend mode (say, instead of mixing alpha, OpenGL will replace alpha with a different calculation)

blurymind commented 7 years ago

@Zylann A new blending mode would be awesome to get. Is there anyone interested in implementing it? Which node will acquire the new blending mode and how would an example gdscript that enables and uses it to freehand erase pixel look like?

I updated the title and description in the first post again to reflect that

Zylann commented 7 years ago

I think it would be an option in the material (or shader?) that let you choose how the result is blended, and it would work on anything that currently can have a blend mode (so anything that the engine can draw, basically).

Currently we have these: image Mix is the most commonly used blend mode. I'm not sure what Premult Alpha does though, and not sure how add, sub and mul treat it either. In Unity shaders, blending is specified as two keywords instead of one, so there might be combination of things at play.

blurymind commented 7 years ago

@Zylann would we still be using gdscript and draw_line method though? Or are we writing a shader?

Zylann commented 7 years ago

You would still draw anything you want like the engine allows you to. I mentionned shaders just for the "replace alpha" part, but it's not really about coding the shader, mostly specifying a blending mode the Godot way so that OpenGL does what you need in the renderer. Like, blend mode "Add" adds color to the image. "Sub" subtracts it. "Mul" multiplies it. It sounds logical to have a way to say "Replace alpha" then, or something close to what OpenGL needs.

This SO threads is a similar problem to yours https://stackoverflow.com/questions/4074955/blending-a-texture-to-erase-alpha-values-softly-with-opengl Same "easy" solutions are being discussed too in case your background is a solid color, but the general blending mode option is also mentionned.

blurymind commented 7 years ago

They mention this:

...If so, instead of "erasing", you could simply paint background over it. That would be somewhat simpler, as you would only change the color and not the blending mod.

Which is pretty much one of the suggestions we came up with here.. Notably: color = Color(VisualServer.get_default_clear_color()) Unfortunately godot doesnt treat that color as a color with 0 alpha that erases other colors - which would the expected behaviour be.

That said, I would take the option of a new blending mode too. Either would solve it. The question now is which one is a lower hanging fruit and does it fit best with the current design? Do we need to have both another blending mode and VisualServer.get_default_clear_color() do what it's supposed to, instead of this: https://user-images.githubusercontent.com/6495061/29914660-45be9be4-8e31-11e7-8ee6-7d014da28662.png

What would the most elegant gdscript method be to set the masking color that erases pixels?

What part of the source code is in charge of blending modes. I wish I knew as much c++ as I do python šŸ–Œ

reduz commented 7 years ago

I think the best way to do this would be:

1) To add a draw_line function to Image 2) To upload the image when it changes 3) To use it as either light or mask for the framebuffer

Zylann commented 7 years ago

@blurymind VisualServer.get_default_clear_color() will never help you draw on the alpha channel, at best it will give you which color to draw in order to "fake" transparency by drawing the same background, but then the image won't be transparent if you want to save it afterwards.

@reduz why not exposing the blend options? They exactly do what an app involving painting would need, and work faster than painting on the CPU and uploading the image every frame (also would miss all features the renderer let us draw). Sure, having a 1px-wide breisenham on CPU would fit this very specific request a bit better than GDScript, but I feel like it would miss a awesome quick win for using the renderer.

Also maintaining a secondary texture as mask would work, but you would need to redraw on it the same things that you draw on the main texture, when not using the eraser, in order to refill alpha to 1. And when it comes to save as image another rendering would occur to flatten those two textures into one. It would maybe work, but in an impractical way.

reduz commented 7 years ago

@Zylann there is not much of a blend option to expose, GPU blending is very very limited, not near as complex as what something like Photoshop can do.

He can also do this using GPU by drawing to another viewport and use it to blend..

Zylann commented 7 years ago

Are you sure it's not supported? The SO thread mentionned this: glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); Also the same kind of options are available to write in Unity shaders.

reduz commented 7 years ago

@Zylann Exposing higher level blending options will not happen, because Godot tweaks the shader and changes the blend function according to what it's doing internally.

In Unity you can do this, but you have to write your own shader passes for forward base, forward add, deferred, shadow, etc. That's not the idea in Godot, as It's meant to be more user friendly.

That said, given this is 2D only, adding color replace and alpha replace models should be possible to the list of 2D blend modes.

blurymind commented 7 years ago

@reduz you're right, having a color replace and alpha replace models would be only useful for 2d drawing anyway. @Zylann makes a good point that it would be beneficial to have it, since getting this sort of functionality in a game or an app would be both much more straightforward and more performant.

I would be very thankful if somebody looks into it and will help in any way possible with testing/examples :)

A drawing app is not the only use case for this I can think of either. There are many interesting things that can be done. Cool editor addons can come out of it

Even a simple pixel editing app addon could help rapid prototyping without leaving the editor. A dirty mockup used to design gameplay can be made by the designer and later used by an artist for sprite dimensions

Zylann commented 6 years ago

While thinking about implementing GPU-based terrain painting and sculpting, I figured out I needed alpha override blend modes (among other things). Indeed, the case is similar to general texture painting, while in this case the texture is used as a "data" rather than forcibly something to draw as is.

What I would need would basically treat the alpha channel like any other channel (R, G, B), which gives the ability to paint on full splatmaps or various other maps that use 4 channels at once. That use case needs a 2D viewport though.

Edit: I realized one problem with this, though... if those blend modes are available in 2D only, but those textures are then used in 3D, that means it could create a constant reimport loop due to the fact VisualServer auto-reimport color space of such textures when used for 3D :x

BastiaanOlij commented 6 years ago

I second the option to add a blend mode which simply says "replace" or simply "off". Basically just turn off blending when rendering with that shader. The alpha (along with the RGB value) will just be written into the frame buffer as is without blending it with the current value.

Bauxitedev commented 6 years ago

I'm running into this issue as well when working on my texture painter. There seems to be no way to decreate alpha by drawing something, aka erasing. So if the alpha of a pixel is 1, there's no way to get it back down to 0. The painting shader is 2D, so a "alpha replace" 2D blend mode would be perfect and solve all my issues.

BastiaanOlij commented 6 years ago

I added this to master a week or so ago, check the render_modes, there should be a blend_disabled there. If you use SCREEN_TEXTURE you can read the current pixel value, do your own modifications, and write back whatever value you want

On Tue, 22 May 2018 at 1:58 am, Bauxitedev notifications@github.com wrote:

I'm running into this issue as well when working on my texture painter https://github.com/Bauxitedev/godot-texture-painter. There seems to be no way to decreate alpha by drawing something, aka erasing. So if the alpha of a pixel is 1, there's no way to get it back down to 0. The painting shader is 2D, so a "alpha replace" 2D blend mode would be perfect and solve all my issues.

ā€” You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/10255#issuecomment-390698250, or mute the thread https://github.com/notifications/unsubscribe-auth/AB2vacvhG9_Lo4fKDJX0gxMzQWybms5Iks5t0uQ9gaJpZM4O0ygy .

-- Kindest regards,

Bastiaan Olij

https://www.facebook.com/bastiaan.olij https://twitter.com/mux213 https://www.youtube.com/channel/UCrbLJYzJjDf2p-vJC011lYw https://github.com/BastiaanOlij

BastiaanOlij commented 6 years ago

Oh look, my PR is even referenced up above :) #18462 :) Couldn't see that on my poor mobile phone

blurymind commented 6 years ago

@BastiaanOlij can you share a simple code snippet demonstrating how this could be applied on a draw operation such as draw_line for example

I kind of already posted a minimal example where it doesnt work because of the feature missing. :) I wanna test it out in the weekend and was wondering what to alter in my little example to make it erase pixels

BastiaanOlij commented 6 years ago

@blurymind check out the shaders in my terrain editor: https://github.com/BastiaanOlij/godot-terrain-edit/blob/master/Shaders/Splatmap-brush.shader

Keep in mind, you need a fresh master build for this, it was only added a week or so ago so this is not in any of the official builds and won't be till 3.1 comes out.

(and also keep in mind that if you do use SCREEN_TEXTURE, it creates a copy of your viewport texture for the first shader that uses it, so you can't do any overlapping logic)

blurymind commented 6 years ago

@BastiaanOlij so it can only be done via a shader? Is there no way to use a simple draw() method? Seems like taking a long route just to be able to erase pixels you painted in a simple pixel editor. It does make a lot more sense in the context of a 3d terrain editor though

BastiaanOlij commented 6 years ago

@blurymind thing is, when you're using viewports you're dealing with a texture that lives in GPU memory (whatever the correct term for that is:)). Shaders are the most effective way to write into that but I do see that in the use case you sketch it feels like overkill :)

However you don't have simple direct access to it.

I guess we could add some sort of interface that allows you to call glTexSubImage2D to overwrite a small part of a texture with a subimage.

blurymind commented 6 years ago

In my usecase, the main problem was that we cant use any of the draw methods to erase pixels - which is required if one is to create a pixel editing tool with a simple eraser brush.

The original idea for the addon was to enable editing pixel art inside godot without leaving it- just the ability to draw lines and erase them with a brush. We got the first part down.

So in order to be able to also delete pixels, I have to now completely abandon the draw_line method - which is really what is expected to be used in the api for such a thing.

I was hoping that one would be able to simply set the color of a line to a specific value - with any alpha value - and that alpha value should be able to overwrite any pixels that the line is drawn ontop of. So to make an eraser - it could be as simple as setting the alpha to 0 and putting the line on an overwrite pixels below instead of composit blend mode.

I think that is what was suggested as another blending mode - surely there is some complexity to achieve it - exposing that functionality to draw methods would make them much more powerful imo. Without it they feel kind of limited to only be able to draw new lines, erase specific lines, or erase the entire canvas. We cant directly erase pixels by drawing lines ontop of them at all

Zylann commented 6 years ago

In my usecase, the main problem was that we cant use any of the draw methods to erase pixels - which is required if one is to create a pixel editing tool with a simple eraser brush.

You can if the viewport in which you draw has a render-target. Any draw method will work, with the appropriate material or shader, nothing complex. This is the actual solution to being able to erase pixels, that's how I prototyped a drawing tool :)

AlvaroAV commented 5 years ago

I managed to delete drawn pixels following @Zylann suggestion.

I have the next Node structure (Control Node --> Viewport, Sprite)

image

The viewport has this properties:

The board node is a Sprite with a CanvasItemMaterial on the Material property: image

On the CanvasItemMaterial the Blend Mode is set to add:

image

Then I can use the method draw_line with an HEX ARGB color to erase previously drawn pixels.

I use next color to delete drawn pixels:

Color("#ff000000")

blurymind commented 5 years ago

Can you share your example project?

On Sun, Mar 10, 2019, 11:12 PM AlvaroAV notifications@github.com wrote:

I managed to delete drawn pixels following @Zylann https://github.com/Zylann suggestion.

I have the next Node structure (Control Node --> Viewport, Sprite)

[image: image] https://user-images.githubusercontent.com/5870043/54092784-9a06cf80-4390-11e9-8ed5-2ca50a0e8cc7.png

The viewport has this properties:

  • Transparent BG = True
  • Clear Mode = Next Frame

The board node is a Sprite with a CanvasItemMaterial on the Material property: [image: image] https://user-images.githubusercontent.com/5870043/54092840-7001dd00-4391-11e9-9a6b-041772880b3a.png

[image: image] https://user-images.githubusercontent.com/5870043/54092799-cfabb880-4390-11e9-927e-2d9947e1ffc5.png

The Blend Mode set to add on the CanvasItemMaterial

Then I can use the method draw_line with an HEX ARGB color to erase previously drawn pixels.

I use next color to delete drawn pixels:

Color("#ff000000")

ā€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/10255#issuecomment-471364039, or mute the thread https://github.com/notifications/unsubscribe-auth/AGMbVS9xDpQSnLGPAT8t0II3Igf0C6XZks5vVZF3gaJpZM4O0ygy .

AlvaroAV commented 5 years ago

@blurymind I'll try to upload an example project with this working on Godot 3.1

I'm quite busy until weekend, I'll let you know when it's ready

enrics commented 5 years ago

Any update on this? I've tried @AlvaroAV method but having the texture in add blend mode is problematic if I intend to have multiple textures stacked, need them on normal mode to be able to paint them properly.

starryalley commented 4 years ago

I also managed to clear pixels using what is suggested above: Using a Viewport and draw inside that Viewport. However I'm using Line2D to implement both draw and eraser.

The drawing Line2D has material blend mode BLEND_MODE_MIX, while the eraser Line2D has material blend mode BLEND_MODE_SUB.

Very simple demo project: https://github.com/starryalley/godot-draw-eraser-demo

akien-mga commented 4 years ago

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

jonSP12 commented 1 year ago

I also managed to clear pixels using what is suggested above: Using a Viewport and draw inside that Viewport. However I'm using Line2D to implement both draw and eraser.

The drawing Line2D has material blend mode BLEND_MODE_MIX, while the eraser Line2D has material blend mode BLEND_MODE_SUB.

Very simple demo project: https://github.com/starryalley/godot-draw-eraser-demo

Eraser: CanvasItemMaterial.BLEND_MODE_SUB no longer works on godot 4

eraser

AThousandShips commented 1 year ago

Please read above, this has been closed for over 3 years, open a proposal if one doesn't exist if you're interested in this feature, but this is long closed and doesn't need resurrection

jonSP12 commented 1 year ago

Please read above, this has been closed for over 3 years, open a proposal if one doesn't exist if you're interested in this feature, but this is long closed and doesn't need resurrection

Ho that's great... then y just have to wait 3 more years until someone else mentions this again !

AThousandShips commented 1 year ago

Open a proposal if you want to see this added, or wait, it's up to you: https://github.com/godotengine/godot/issues/10255#issuecomment-634037008

jonSP12 commented 1 year ago

Very simple demo project: https://github.com/starryalley/godot-draw-eraser-demo

its Canvas Material LIGHT MODE In function Ready add material LIGHT_MODE_NORMAL and LIGHT_MODE_LIGHT_ONLY

func _ready(): draw_material = CanvasItemMaterial.new(); draw_material.blend_mode = CanvasItemMaterial.BLEND_MODE_MIX draw_material.light_mode = CanvasItemMaterial.LIGHT_MODE_NORMAL; ##############

erase_material = CanvasItemMaterial.new(); erase_material.blend_mode = CanvasItemMaterial.BLEND_MODE_SUB; erase_material.light_mode = CanvasItemMaterial.LIGHT_MODE_LIGHT_ONLY; ########

pass

eraser1

----------------//----------//---------------

eraser2