wlroots/render/vulkan/README-alpha-blend

56 lines
2.1 KiB
Text
Raw Normal View History

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).