Canvas and alpha

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Tabaqui
Prole
Posts: 25
Joined: Tue Mar 24, 2020 2:47 pm
Location: Italy

Canvas and alpha

Post by Tabaqui »

Hi all!
I have some troubles understanding how alpha and canvases work together:

I'm drawing a texture with some transparency on a canvas and then drawing the canvas itself on the screen. I've noticed that, if i don't use the canvas and i draw directly the texture on screen, i obtain a different effect. By searching the forums i've found out that i need to set the blend mode to "premultiplied" before drawing the canvas, and it works as expected.

The problem occurs when i need to apply an additional transparency on what i draw (by using a shader or by changing the color with love.graphics.setColor(1, 1, 1, myAlpha)). Even with premultiplied blend mode i can't get what i need.

Am i missing something?
canvas.png
canvas.png (12.29 KiB) Viewed 2285 times
You can find a minimal .love file in the attachments that reproduces the problem.

EDIT: I have no control over what is drawn inside the canvas, so let's say i can use setColor only before drawing the canvas on the screen.
Attachments
Canvas.love
(1.01 KiB) Downloaded 62 times
User avatar
pgimeno
Party member
Posts: 2605
Joined: Sun Oct 18, 2015 2:58 pm

Re: Canvas and alpha

Post by pgimeno »

Thanks for the test case. The problem is that the default shader is not designed for applying alpha in premultiplied mode. You can try using this shader when drawing the canvas to the screen in premultiplied mode with extra alpha:

Code: Select all

local premult_shader = love.graphics.newShader
[[
  vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos)
  {
    return colour.a * vec4(colour.rgb, 1.0) * Texel(tex, texpos);
  }
]]
Edit: Changed 1 to 1.0 which is more compatible.
grump
Party member
Posts: 719
Joined: Sat Jul 22, 2017 7:43 pm

Re: Canvas and alpha

Post by grump »

Or, if you can, use a shader that doesn't premultiply alpha when drawing to the Canvas. Which should be the default anyway imo, because these stupid special cases are a pain to deal with. It sucks.

Code: Select all

// use this instead of no shader to disable premultiplication
vec4 effect(vec4 col, Image tex, vec2 uv, vec2 fc) {
	return col * Texel(tex, uv);
}
User avatar
pgimeno
Party member
Posts: 2605
Joined: Sun Oct 18, 2015 2:58 pm

Re: Canvas and alpha

Post by pgimeno »

grump wrote: Fri Mar 19, 2021 1:35 pm Or, if you can, use a shader that doesn't premultiply alpha when drawing to the Canvas. Which should be the default anyway imo, because these stupid special cases are a pain to deal with. It sucks.

Code: Select all

// use this instead of no shader to disable premultiplication
vec4 effect(vec4 col, Image tex, vec2 uv, vec2 fc) {
	return col * Texel(tex, uv);
}
That's equivalent to the default shader, isn't it? Have you tried it in Tabaqui's example?

Yes, it's a pain and it sucks. But that's OpenGL. The real alpha compositing formula is not linear, and OpenGL can only perform compositing using linear formulas. However, if you start with a canvas initialized to R,G,B,A=0,0,0,0, and apply normal blending to draw on it, then draw the canvas to the screen using premultiplied alpha mode, the result is the same as if using true alpha compositing to draw to the canvas, and then true alpha compositing to draw the canvas to the screen, so that's one way we have to work around that OpenGL limitation.

Edit: The alternative is drawing always to the canvas using a shader for true alpha compositing, and the shader is more complex than the one you've posted:

Code: Select all

  extern Image canvas;

  vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos)
  {
    vec4 src = colour * Texel(tex, texpos);
    vec4 dst = Texel(canvas, scrpos);
    vec4 res;
    res.a = src.a + dst.a * (1.0 - src.a);
    res.rgb = (src.rgb * src.a + dst.rgb * dst.a * (1.0 - src.a)) / res.a;
    return res;
  }
and don't forget to pass the canvas you're drawing to (and you can't use other shaders on top of it).

Edit 2: A google search for 'alpha compositing with OpenGL' leads to this post: https://apoorvaj.io/alpha-compositing-o ... ied-alpha/ which is quite interesting and presents a shader that is equivalent to the above. It also explains why it works like that from the hardware perspective. Quite enlightening.
grump
Party member
Posts: 719
Joined: Sat Jul 22, 2017 7:43 pm

Re: Canvas and alpha

Post by grump »

Uhm, what the hell. I must've been thinking about another engine or use case. Sorry about that. I could've sworn the rgb multiplication is done in the shader when using Canvas.

Anyway, for this exact copy-texture-to-canvas case you can use blendmode 'none' (or is it 'replace'?) to copy the color channels, but that's obviously not a general solution for more complex cases.
User avatar
Tabaqui
Prole
Posts: 25
Joined: Tue Mar 24, 2020 2:47 pm
Location: Italy

Re: Canvas and alpha

Post by Tabaqui »

Thank you guys, pgimeno solution works as expected although i can't figure out why. Inside my mind the canvas should have been an exact copy of the texture after drawing inside it, so i can't really understand what was the problem. I guess i should study the topic more because i don't like to copypaste without fully understanding what the code does.

I've thought using the "replace" blend mode when drawing to the canvas but it won't work in the real project because i'm drawing multiple stuff in it.
grump
Party member
Posts: 719
Joined: Sat Jul 22, 2017 7:43 pm

Re: Canvas and alpha

Post by grump »

Tabaqui wrote: Fri Mar 19, 2021 4:31 pm Inside my mind the canvas should have been an exact copy of the texture after drawing inside it
When you draw something in mode alpha + alphamultiply, this formula is applied:

Code: Select all

dst.rgb = src.rgb * src.a + dst.rgb * (1 - src.a)
For any alpha < 1, this results in darkened colors in the target canvas. They are not the same as in the source texture anymore.

If you draw the result of this with the same formula, the colors become even darker. alpha + premultiplied does this instead:

Code: Select all

dst.rgb = src.rgb + dst.rgb * (1 - src.a)
This blendmode does not do the src.rgb * src.a operation a second time, and gives correct results.

As for the setColor problem, this seems to be a quirk with how vertex colors are handled in premultiplied mode, but I don't want to get the details wrong again so I'll leave that to pgimeno.
User avatar
pgimeno
Party member
Posts: 2605
Joined: Sun Oct 18, 2015 2:58 pm

Re: Canvas and alpha

Post by pgimeno »

Yeah, premultiplied alpha means that colours are darkened proportionally to the alpha. This means that if more alpha needs to be applied, more darkening is necessary, but the default shader doesn't do that; the formula to do it is the one in the shader I gave. In fact, maybe Löve should take into account the current alpha mode in the shader, to decide whether to apply the current formula or the premultiplied one.
User avatar
slime
Solid Snayke
Posts: 2932
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Canvas and alpha

Post by slime »

The premultiplied alpha blend mode expects premultiplied colour inputs (textures, global color, etc). The global color used in the default shader comes from love.graphics.setColor. The default shader supports all blend modes, but only when the inputs to it match the blend mode. It's only when the inputs don't match the blend mode that custom shader logic is needed.

In other words, when using the default shader with the premultiplied alpha blend mode (provided you actually are drawing a texture which has premultiplied alpha in its pixels), you'll need to premultiply the global color's alpha when calling love.graphics.setColor.

Code: Select all

love.graphics.setColor(red * alpha, green * alpha, blue * alpha, alpha)
User avatar
pgimeno
Party member
Posts: 2605
Joined: Sun Oct 18, 2015 2:58 pm

Re: Canvas and alpha

Post by pgimeno »

Well, yes, premultiplying the alpha when calling setColor would obtain the same result. Is there a use case for having alpha blending mode = premultiplied, a setColor call with alpha < 1, and a colour passed to setColor that does not have premultiplied alpha? Premultiplying alpha is something I'd expect Löve to do automatically to the colour, when the alpha mode is premultiplied. This thread is about the confusion that Löve caused to Tabaqui for not having that automatically done for them.

Edit: Well, maybe there's one. Mode = replace / premultiplied sets the colour. It's not so clear cut, it seems.

Come to think about it, it doesn't even have to be a custom shader; just a preprocessing of the colour when Löve sends it to the shader internally would suffice.
Post Reply

Who is online

Users browsing this forum: Bing [Bot], darkfrei, Google [Bot] and 35 guests