我正在寻找一种优化alpha混合的方法,但是对于alpha的两种颜色(与问题How to alpha blend RGBA unsigned byte color fast?有什么不同)
最初我使用了一个浮点数的解决方案(RGB范围从0.0f
到255.0f
,A范围从0.0f
到1.0f
):
inline void alphaBlend(Color& baseColor, Color targetColor)
{
float newAlpha = (1 - targetColor.A) * baseColor.A + targetColor.A;
baseColor.R = ((1 - targetColor.A) * baseColor.A * baseColor.R + targetColor.A * targetColor.R) / newAlpha;
baseColor.G = ((1 - targetColor.A) * baseColor.A * baseColor.G + targetColor.A * targetColor.G) / newAlpha;
baseColor.B = ((1 - targetColor.A) * baseColor.A * baseColor.B + targetColor.A * targetColor.B) / newAlpha;
}
字符串
我改变了算法,使其适用于unsigned int
RGBA颜色。我将所有对alpha的引用替换为(alpha / 255),然后更正了公式,使值仍然在正确的范围内。
baseColor.R = ((1 - targetColor.A) * baseColor.A * baseColor.R + targetColor.A * targetColor.R) / newAlpha;
Shorthand (targetColor.A -> tA etc.):
R = ((1 - tA) * bA * bR + tA * tR) / newAlpha
(introducing 255-based alpha requires replacing all A instances with A/255)
= ((1 - (tA / 255)) * (bA / 255) * bR + (tA / 255) * tR) / (newAlpha / 255)
(remove 255 from the denominator's denominator)
= (((1 - (tA / 255)) * (bA / 255) * bR + (tA / 255) * tR) * 255) / newAlpha
(get rid of direct alpha divisions by 255 by multiplying parethesis by 255/255)
= (( ((255 - tA) * bA * bR) / 255^2 + (tA * tR) / 255) * 255) / newAlpha
(multiplying by the last 255 causes denominators to reduce)
= ( ((255 - tA) * bA * bR) / 255 + (tA * tR * 255) / 255 ) / newAlpha
(Pushing numerator's denominator (255) to the denominator)
= ( (255 - tA) * bA * bR) + (tA * tR * 255) ) / (255 * newAlpha)
(Expanding first multiplication in numerator)
= ( 255 * bA * bR - tA * bA * bR + tA * tR * 255) / (255 * newAlpha)
^^^^^^^^^^^^ ^^^^^^^^^^^^^
(reordering not to fall below 0 during calculations)
= ( 255 * bA * bR + tA * tR * 255 - tA * bA * bR ) / (255 * newAlpha)
(grouping to minimize multiplications)
= ( (ba * bR + tA * tR) * 255 - tA * bA * bR ) / (255 * newAlpha)
(introducing bit shifting - losing precision, but in an acceptable range)
~= ( ((ba * bR + tA * tR) << 8) - tA * bA * bR) / (newAlpha << 8)
型
我设法编写了以下代码:
inline void alphaBlend(IntColor& baseColor, IntColor targetColor)
{
unsigned int a = (((baseColor.A + targetColor.A) << 8) - targetColor.A * baseColor.A) >> 8;
if (a > 0)
{
unsigned int divisor = a << 8;
unsigned int baseAR = baseColor.A * baseColor.R;
baseColor.R = (((targetColor.A * targetColor.R + baseAR) << 8) - (baseAR * targetColor.A)) / divisor;
unsigned int baseAG = baseColor.A * baseColor.G;
baseColor.G = (((targetColor.A * targetColor.G + baseAG) << 8) - (baseAG * targetColor.A)) / divisor;
unsigned int baseAB = baseColor.A * baseColor.B;
baseColor.B = (((targetColor.A * targetColor.B + baseAB) << 8) - (baseAB * targetColor.A)) / divisor;
baseColor.A = a;
}
else
{
baseColor.R = 0;
baseColor.G = 0;
baseColor.B = 0;
baseColor.A = 0;
}
}
型
这一更改将样本数据的渲染从27559 ms减少到17751 ms。由于alpha混合似乎是渲染工作流程中最常见的操作,我很好奇是否有方法进一步优化它。
我想同时对R和B进行计算,但不幸的是,在某些情况下,计算将超过两个字节(例如,如果bA = bR = tA = tR = 255,减法的左部分将等于33162750 = 0x 1faa 05 fe)。
有没有其他的优化方法可以让这段代码更快?
**编辑:**回复评论:
- 目标体系结构为x64,目标处理器可能为Intel Core系列
- 输入类型保证为32位RGBA
- 内存布局为BGRA(8888)
- 关于SIMD,我的应用程序是一个矢量动画渲染器。每个对象都在一个单独的位图上渲染,然后阿尔法混合到结果中,因为每个对象都可能应用了阿尔法/遮罩/变换/效果,或者可能由多个子对象组成,每个对象也可能应用了这些。
- 编译器是来自Microsoft Visual Studio 2022的编译器。应用程序仅适用于Windows。
1条答案
按热度按时间l7mqbcuq1#
我把这个答案留给那些也在寻找基于整数计算的alpha混合两种颜色的alpha(也就是说,允许“背景”或“基础”颜色也是半透明的)的人。它不是 * 非常 * 快,但肯定比它的浮点等价物快。
不幸的是,我问题中的代码存在缺陷,有时会给出256的结果,这在某些情况下会导致丑陋的黑色像素(
(unsigned char)256 == 0
)。下面的代码提供了解决方案,也可以作为正确性检查。它验证:
[0..255]
的边界从信息上来说,浮点alpha通常会保留在[0.0f..1.0f]范围内,现在它被规范化为[0.0f..255.0f],这样我就可以将它与它的int对应项进行比较。
验证代码/解决方案如下。
字符串
结果令人惊讶地好-应用程序测试了所有可能的颜色和阿尔法组合,并且与浮点计算不同的那些组合(我认为是有效的)的百分比低于1%(组合总数为4 294 967 296):
型
阿尔法混合的流行优化是通过替换(
* 255, / 255
)操作,分别(<< 8, >> 8
),等于(* 256, / 256
)。由于我们需要乘和除以255而不是256,优化的代价是精度下降。坏消息是错误结果的数量急剧增加,但好消息是,误差仍然不超过alpha和颜色的单位值:的字符串
所以现在:
如果你决定选择最后一个解决方案,不要忘记在这里发布另一个答案-我相信每个人都会从快速阿尔法混合算法中受益。