mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-04-14 08:22:25 -04:00
The Vulkan renderer performs alpha blending in linear RGB space, which preserves hue better than blending sRGB-encoded values directly (as the gles and pixman renderers do), but unfortunately tends to give a too-bright result when blending dark and light colors. (In desktop usage, this especially affects dark, semi-transparent tooltips, which appear significantly more transparent than expected, affecting readability if light text underneath shows through.) This is a novel (I think) approach to compensating for this effect by adjusting the alpha value of the source texture - basically the result is that dark semi-transparent pixels are made a little more opaque, while light semi-transparent pixels are made a little more transparent. Alpha values of 0 and 1 are unchanged. I am somewhat new to science of color blending (Björn Ottosson's page, "How software gets color wrong" is very enlightening) but I think this approach makes at least a little bit of sense theoretically, and the result seems to me subjectively to be an improvement. Analysis from an expert on the subject would be greatly appreciated. v2: compensate alpha in solid color conversions also v3: un-premultiply average value for solid color conversion
55 lines
2.1 KiB
Text
55 lines
2.1 KiB
Text
NOTE ON ALPHA BLENDING IN THE VULKAN RENDERER
|
|
|
|
Vulkan internally performs alpha blending in linear RGB color space.
|
|
While this preserves hue better than blending in sRGB space, it tends to
|
|
produce a too-bright result when blending dark and light colors. (See
|
|
illustrations in "How software gets color wrong" by Björn Ottosson,
|
|
https://bottosson.github.io/posts/colorwrong/.)
|
|
|
|
(In desktop usage, this especially affects dark, semi-transparent
|
|
tooltips, which appear significantly more transparent than expected,
|
|
affecting readability if light text underneath shows through.)
|
|
|
|
This effect can be compensated somewhat by adjusting the alpha channel
|
|
to make dark overlay colors a bit more opaque. The Vulkan renderer
|
|
currently performs this compensation only for sRGB source textures.
|
|
|
|
To keep the math manageable, the compensation equation is derived using
|
|
a lot of assumptions and approximations, namely:
|
|
|
|
1. the perceptual lightness of a given source pixel is approximately
|
|
the average of the RGB values in sRGB encoding (0-1 range)
|
|
|
|
2. alternately, the perceptual lightness is approximately the square
|
|
root of the average of the RGB values in linear encoding (the power
|
|
should really be 1/2.4, but 1/2 is close enough)
|
|
|
|
3. the lightness of the pixel underneath (in the surface being blended
|
|
over) is unknown and thus assumed to be 0.5. (This does make the
|
|
compensation less accurate when the underlying surface is very dark
|
|
or light, but it's still much better than nothing.)
|
|
|
|
If we could blend a pixel with lightness value v2 over a lightness value
|
|
v1 in a theoretical perceptual color space, the resulting lightness
|
|
should be simply:
|
|
|
|
(1 - alpha)*v1 + alpha*v2
|
|
|
|
However, alpha blending in linear space instead results in a perceptual
|
|
lightness of approximately:
|
|
|
|
sqrt((1 - alpha)*(v1^2) + alpha*(v2^2))
|
|
|
|
To compensate, we would like to solve for a new alpha value a' where:
|
|
|
|
sqrt((1 - a')*(v1^2) + a'*(v2^2)) = (1 - a)*v1 + a*v2
|
|
|
|
Solving gives:
|
|
|
|
a' = ((v2 - v1)*a^2 + 2*v1*a) / (v2 + v1)
|
|
|
|
Assuming v1 = 0.5 simplifies this to:
|
|
|
|
a' = ((v2 - 0.5)*a^2 + a) / (v2 + 0.5)
|
|
|
|
which is the equation used in the shader (texture.frag).
|