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

Shaders rendered on a transparent viewport are darker #17574

Open
LikeLakers2 opened this issue Mar 17, 2018 · 35 comments
Open

Shaders rendered on a transparent viewport are darker #17574

LikeLakers2 opened this issue Mar 17, 2018 · 35 comments

Comments

@LikeLakers2
Copy link
Contributor

LikeLakers2 commented Mar 17, 2018

Godot version:
Godot Engine v3.0.2.stable.official

OS/device including version:
Windows 10 Home v1709 (Fall Creator's Update); Nvidia GTX 660 (I don't believe this is specific to my GPU, though)

Issue description:
Shaders that get rendered on a transparent viewport (with nothing behind them) end up darker than they normally should. This can be solved by moving the object with a shader outside of any custom viewport, but I imagine that would end up getting messy quick, if viewports are being relied upon in some way.

What's expected:
image

What actually happened:
image

I believe this has to do with how the transparent viewport itself is handled, as having something behind the shader under the viewport will cause the shader to not be dark. (Try moving the ColorRect to under the Viewport, above ShaderPanel, in the reproduction project to see what I mean.)

Steps to reproduce:

  1. Create a viewport, and enable the "Transparent BG" option, as well as setting up the size.
  2. Create a Panel node (or really any node that supports a shader, I just use a Panel node) under that viewport.
  3. Give the Panel node a shader that will, in some way, return some transparent pixels. The one included in the reproduction project below (shader.tres) will work for this purpose, and should show the effect fairly well due to creating a red-to-transparent gradient like in the images above.
  4. Create a sprite, and set its texture to the ViewportTexture of the viewport.
  5. (Optional, but makes the effect easier to see) Make a ColorRect, and place it behind the Sprite.
  6. When the panel is under the Viewport, the image that the shader makes should be darker than expected. Moving the panel out of the Viewport's children will show the expected result.

Minimal reproduction project:
Shader Blackness.zip

@Calinou
Copy link
Member

Calinou commented Aug 22, 2018

I wonder if this is related to premultiplied alpha in some way. This is available as an import setting in textures, but I'm not sure if you can toggle it in viewports.

@reduz
Copy link
Member

reduz commented Sep 6, 2018

It is as expected, I was thinking about premultiplying alpha as a post process in viewports, in case you want to use premult, but this wont happen until 3.2

@reduz reduz modified the milestones: 3.1, 3.2 Sep 6, 2018
@LikeLakers2
Copy link
Contributor Author

Well then, is there any known workaround? I haven't been able to find one myself. This issue seems fairly annoying as my use case for rendering shaders on a viewport, instead of on a node in the root scene, is so I can cut off edges like this:

Cutting off edges

image

But I don't know if there's any other nodes or methods that will let me do that.

@sketchyfun
Copy link
Contributor

Would definitely be great to have this added, it seems like I'm stuck with this problem as my tool uses viewports and alpha blended particles. I thought I'd had some success with using a premultiplied shader when drawing the particles, but that screws up as soon as I change their lifetime colour. Guess I'll have to wait until 3.2..

image

@MrCrowley2
Copy link

ran into the same problem with some custom post processing tests and transparent objects in an extra viewport on the current 3.1 Beta

https://i.imgur.com/fQBRMHP.png

I think I can somewhat confirm the premultiplied alpha as a cause.

In the shader of the object in the vieport I used

ALBEDO = vec3(1,1,1)*dist; ALPHA = dist;

and in the shader of the ViewportContainer I used
COLOR.rgb = col.rgb / col.a; COLOR.a = col.a;

and the problem seems to be nearly gone (small differences remain).
https://i.imgur.com/cC27RbQ.png

@rokasv
Copy link

rokasv commented Mar 19, 2019

I'm having the same issue, rendering UI in a separate viewport prevents the UI from using transparency:
Capture
The grey bar is supposed to merge into the background (it is the same color as the background with 0.5 alpha on a viewport above it), but the blackened alpha makes it intolerable.

@akien-mga akien-mga modified the milestones: 3.2, 4.0 Dec 14, 2019
@clayjohn
Copy link
Member

Looks like sRGB -> linear issues. Make sure everything is kept in linear space until the very end.

@LikeLakers2
Copy link
Contributor Author

LikeLakers2 commented Mar 12, 2020

@clayjohn Could you explain? All I'm doing is running a shader that outputs red, with an alpha value that lowers the farther it is from a point -- then the output of that shader is going through a transparent viewport. I'm not sure what sRGB space and linear space have to do with this.

@clayjohn
Copy link
Member

@LikeLakers2 Depending on your asset workflow there may be sRGB -> linear conversions going on.

If the hint of the texture you are using is "hint_albedo" it may force an sRGB -> linear conversion.

Depending on the texture settings/viewport settings your viewport may be converting from sRGB -> linear after being rendered to (in anticipation of being used as a texture) in which case you would need to set "hint_albedo".

@LikeLakers2
Copy link
Contributor Author

LikeLakers2 commented Mar 12, 2020

@clayjohn The shader code I used for the images in the original posts contains no uniforms, though -- that is, the gradient shown is made entirely with code:

shader_type canvas_item;

void fragment() {
	vec2 cur_pos = UV.xy;
	vec2 mid_pos = vec2(1.0, 1.0);
	
	float dis = 1.0 - clamp(distance(cur_pos, mid_pos), 0.0, 1.0);
	COLOR = vec4(1.0, 0.0, 0.0, dis);
}

There are no textures to speak of aside from the texture that the shader outputs to the Viewport, and the texture that the Viewport outputs to the Sprite -- so I'm not sure where hint_albedo would apply here.

@clayjohn
Copy link
Member

@LikeLakers2 I'm talking about when rendering the Viewport to the screen.

@LikeLakers2
Copy link
Contributor Author

LikeLakers2 commented Mar 13, 2020

@clayjohn I'm still not following you. Honestly, I'm becoming more confused with each comment.

Please do me a favor if you think it has to do with sRGB -> linear conversions: Open up the reproduction project in the original post, and see if you can fix the issue without changing the structure of the scene tree. If you can, tell me how to do it.

@devolonter
Copy link
Contributor

Adding render_mode blend_premul_alpha to shader code fix similar issue to me. Example of usage:

shader_type canvas_item;
render_mode blend_premul_alpha;

Hope it helps to you too

@LikeLakers2
Copy link
Contributor Author

@devolonter That seems to fix my issue, at least with my reproduction project. Thank you.

That said, it doesn't sound quite like an obvious solution to this problem, unless you know that this is an issue related to pre-multiplied alpha -- which, given Godot's target demographic, doesn't sound like something I would expect the average user to know. In fact, I only knew about the term once Calinou and reduz chimed in.

@Calinou @akien-mga (not sure which of you to ping, both of you seem appropriate) Would we consider this a fixed issue since we have a solution? Or should the issue be kept open until something else can be done to help this issue?

@Calinou
Copy link
Member

Calinou commented May 13, 2020

We should still find a way to document the workaround, but I'm not sure where we should add this information. Any ideas?

@Calinou Calinou added topic:shaders and removed bug labels May 13, 2020
@LikeLakers2
Copy link
Contributor Author

@Calinou I fear that people may not look in the documentation, seeing as how this may come off as a bug... or if they do look in the documentation, it may not be quite obvious where to look. Do they look under Viewport? Sprite? Shader? etc.

Perhaps, if a viewport is marked as transparent, we could scan for nodes under it that use shaders, and emit a warning next to the Viewport node if any of them are not using render_mode blend_premul_alpha. It feels like a bit of a hack, but I think it's the best thing we can do without just defaulting premultiplied alpha to on.

Otherwise, if we aren't going to add a warning next to the Viewport node, it may be best to just document it in as many relevant places as possible, so that we can ensure people will notice.

@nazgum
Copy link

nazgum commented Nov 21, 2020

It seems not just for shaders, I tried making a minimap in a viewport, using a simple tilemap with colored squares, and if I made the white square 50% opacity, it would render differently inside the viewport:

Viewport with transparent_bg off:  looks correct
Viewport with transparent_bg on:   looks way darker

Edit: I attempted to work around the issue applying the suggested shader above to the tilemap node in my viewport, to set:

shader_type canvas_item;
render_mode blend_premul_alpha;

And it had no effect, still renders darker.

@devolonter
Copy link
Contributor

@nazgum your issue with mipmaps can be related this issue #43342

@LikeLakers2
Copy link
Contributor Author

@devolonter They said minimap, not mipmaps. Also, they're describing the same issue that my original post in this issue had.

@devolonter
Copy link
Contributor

@LikeLakers2 oops, my bad. Perhaps because I spent a whole day to find a similar problem with mipmaps 😅

@deakcor
Copy link
Contributor

deakcor commented Feb 20, 2021

The viewport fills the rgb color with black color so to fix it with a shader:

shader_type canvas_item;

void fragment(){
	vec4 color = texture(TEXTURE,UV);
	vec3 color_fix = color.rgb/color.a;
	COLOR = vec4(color_fix,color.a);
}

image

@ghost
Copy link

ghost commented Jul 31, 2021

Has anybody applied a successful workaround when doing multi-pass post-processing with ViewportContainers from a 3D Viewport? Nothing seems to work in that case...

@Jummit
Copy link
Contributor

Jummit commented Nov 26, 2021

Still valid in 3.4. This needs to be documented somewhere, I just spend a whole day figuring out why my shader was producing wrong results.

@clayjohn
Copy link
Member

clayjohn commented Jun 7, 2022

This may have been fixed with the recent changes to Viewport transparency and post processing (coming in 3.5) Can someone please test this once 3.5 releases and confirm whether it is still a problem?

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 11, 2022

I have what seems to be a similar or related issue (writing here to document for people with similar issues)

When I assign an Image Texture to a Sprite in a Viewport I transparent areas darken:

drawing_with_viewports_transparency_issue01.mp4

This is my project:

drawing_with_viewports

drawing_with_viewports_screenshot

Code:

extends Sprite

onready var capture_viewport = $"../../.."
onready var capture_layer = $"../../../capture_layer"
onready var viewport = get_parent()


func _process(_delta):
	if Input.is_action_just_pressed("LMB"):
		viewport.render_target_clear_mode = Viewport.CLEAR_MODE_NEVER
	elif Input.is_action_just_released("LMB"):
		render_to_texture()
		viewport.render_target_clear_mode = Viewport.CLEAR_MODE_ALWAYS
	
	if Input.is_key_pressed(KEY_R):
		var _reload = get_tree().reload_current_scene()
	
	if Input.is_action_just_pressed("Save"):
		var viewport_data = capture_viewport.get_texture().get_data()
		viewport_data.flip_y()
		var filepath = str(OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP),"/drawing.png")
		viewport_data.save_png(filepath)
	
	global_position = get_global_mouse_position()

func render_to_texture():
	var viewport_data = capture_viewport.get_texture().get_data()
	viewport_data.flip_y()
	var img_tex = ImageTexture.new()
	img_tex.create_from_image(viewport_data)
	capture_layer.texture = img_tex

Minimal Reproduction Project Download:
Drawing_with_Viewports_transparency_issue.zip

I've tried pretty much everything I could think of. Made a Reddit post and TheDuriel lead me here. I also tried to apply the fixes mentioned here in various ways, most of it did not work. However eventually:

Adding

shader_type canvas_item;
render_mode blend_premul_alpha;

to the capture_layer fixed the "getting darker with every stroke", while adding the same material to the ViewportContainer2 fixed the overall darkness of transparent areas.

@clayjohn : I also tested this project in Godot 3.5 RC6 without noticing any difference to Godot 3.4.4, before and after the fix.

@goakley
Copy link

goakley commented Jul 19, 2022

@golddotasksquestions thank you so much for sharing this. I was running into a similar problem where my viewport textures were taking on a black border that didn't appear when using the texture outside of a viewport. It seems that applying a very simple premult shader to the top-level node(s) in a viewport is the best workaround (for now).

See the picture below, in which the middle image has a small black border at the points where the left image became transparent. Using a premult blend mode shader on the viewport textures fixes the issue (right image). This was tested in v3.4.4.stable.arch_linux with a GLES2 project: MRP.zip

comparison

@Ariorick
Copy link

Ariorick commented Jul 31, 2022

My issues are the same as in the first post. With transparent_bg=true everything semi-transparent gets darkened

Currently, to fix this I'm setting shaders or adding this code to shaders on everything that has semi-transparency, and it seems to fix the darkening, but it's very inconvenient. Here's the shader – use it or correct me if I'm doing it wrong

This is for canvas item shaders:

shader_type canvas_item;
render_mode blend_premul_alpha;

void fragment() {
    COLOR = result;
    COLOR.rgb = result.rgb * result.a;
    COLOR.a = result.a;
}

and this code on ViewportContainer shader:

void fragment() {
    vec4 col = texture(TEXTURE, UV);
    if (col.a != 0.0) {
        COLOR.rgb = col.rgb / col.a;
        COLOR.a = col.a
    } else {
        COLOR = col
    }
}

Is there going to be an option to simplify this process somehow?

@SuzukaDev
Copy link

My problem is (was) maybe a bit different, I'm making a single texture from 4 different textures (each texture for each channel (RGBA)), rendering them on a Viewport, and getting the result texture.

I wanted each channel to remain intact and not be premultiplied by the alpha, but I got:

This (with the default blend mode in the shader)

(This is the (single) result texture showing each channel separately)
imagen

(Each channel color is being premultiplied by the alpha)

And after adding render_mode blend_premul_alpha; from this comment (thank you so much!)...

...I got the result I wanted

imagen

I'm commenting this since in my case it wasn't obvious, I wasn't looking for the colors to be premultiplied, but adding the premultiply_alpha blend ironically made the texture to not be premultiplied by the alpha. Maybe I'm missing something, but it is confusing.

IMHO it should be documented somewhere, maybe add some more text to the Viewport's tooltip on the transparent_bg, maybe some warning/clarification at the end, that enabling this can cause some unexpected behaviour on the alpha of the texture. Or in the Viewport documentation, where it says the viewport is flipped vertically by default. Just to make the users aware.


@Ariorick I'm not sure the result you are trying to achieve and the result you are having, but based on my experience, I'd try to delete the render_mode blend_premul_alpha; on the ViewportContainer shader and see if you can avoid that result.rgb * result.a
Also, that if (col.a != 0.0) could cause some weird artifacts when the alpha is near 0.

For the people loading images directly to the viewport, try to check the format you are saving your pngs. There are some formats that premultiply the alpha when saving the image.

@stefano-elysium
Copy link

We're also experiencing this issue. We're dynamically generating images of sprite sheeted characters for optimization purposes and this is causing a lot of problems.

@ALBATI-2005
Copy link

I ran into this exact issue, and I think it's kind of strange it still hasn't been fixed... I mean, if the workaround is simply some shader code applied here and there, why not simply make Godot write this same shader code and apply it automatically whenever "Transparent Bg == true"?

@someguynamedjosh
Copy link

This happens even when you make a simple material and try to make it semi-transparent. Only the alpha of the albedo color has been adjusted between these two screenshots:

Screenshot_20220920_170919
Screenshot_20220920_170904

@bluegreen1024
Copy link

I am having a similar issue in Godot v3.4.4 stable, but I am using a 3D viewport, a spatial shader material with alpha = 1.0, and no display texture at all. I just have two cameras looking at (different parts of) the same object, with one rendering to the root viewport and the other rendering to a separate viewport that I created. This second viewport has transparent background enabled.

image

In the image above, both the root viewport and the secondary viewport node render to the entire screen, but the root viewport shows through the transparent background of the second viewport. You can see the color difference between the left side of the screen (root) and the right side (secondary) - the right side is substantially darker.

Can someone clarify if this is the same issue, and if so, is there a fix or workaround for spatial materials?

@bluegreen1024
Copy link

I'm somewhat confused by this, because I'm not using my secondary viewport's output as input to another shader at all. Also, the texture should be opaque anyway: in fact I just tested it by literally setting the alpha channel to 1.0, and the problem persists. Are you sure this only happens with semi-transparent materials, or is this a different bug?

@ALBATI-2005
Copy link

I'm somewhat confused by this, because I'm not using my secondary viewport's output as input to another shader at all. Also, the texture should be opaque anyway: in fact I just tested it by literally setting the alpha channel to 1.0, and the problem persists. Are you sure this only happens with semi-transparent materials, or is this a different bug?

Even when no shader is applied at all, the problem occurs. In fact, the fix is made simply by adding this shader code:
shader_type canvas_item;
render_mode blend_premul_alpha;

But, as for the problem persisting even when opaque, that's strange...

@Calinou
Copy link
Member

Calinou commented Sep 28, 2022

Even when no shader is applied at all, the problem occurs. In fact, the fix is made simply by adding this shader code:
shader_type canvas_item;
render_mode blend_premul_alpha;

This render mode doesn't exist in 3D – you have to emulate it within the shader. See godotengine/godot-proposals#3431.

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

No branches or pull requests