对于底图颜色和透明度[c0,1],混合颜色[c1,a1],再混合颜色[c2,a2]。(a1,a2为0-1的不透明度,c0,c1,c2为rgb中任何一个颜色分量,底图为rgb,不透明度为1)
记作:[c0,1] blend [c1,a1] blend [c2,a2] = [cdst,adst]
通常:不透明颜色混合一个半透明颜色,结果为不透明,即:
[c0,1] blend [c1,a1] = [c0*(1-a1) + c1*a1,1]
所以[cdst,adst] = [(c0*(1-a1) + c1*a1)*(1-a2) + c2*a2,1]
又我们希望:[c0,1] blend [c1,a1] blend [c2,a2] == [c0,1] blend ([c1,a1] blend [c2,a2])
即,后面两个透明颜色先混合之后,最后与底图混合,颜色不变,即支持结合律
令 [c1,a1] blend [c2,a2] = [cx,ax],
[c0,1] blend [cx,ax] = [c0*(1-ax) + cx*ax,1] = [cdst,1],
1-ax = (1-a1)*(1-a2) => ax = a1+a2-a1*a2(对称的表达式,最终透明度与混合先后无关,且越混合越不透明)
cx = (c1*a1*(1-a2) + c2 * a2) / ax
即 [c1,a1] blend [c2,a2] = [ (c1 * a2 *(1-a2) + c2 * a2) / adst, adst=(a1+a2-a1*a2)]
由于最终的cdst需要除以adst,不够简洁,所以出现了预乘alpha的混合公式:
[Cdst,adst] = [C1,a1] blend [C2,a2] = [C1 * (1-a2) + C2,a1*(1-a2) + a2],C1 = c1*a1,C2=c2*a2,Cdst = cdst*adst
对应于Opengl中设置
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
由于,实际使用的颜色,大多数都是非预乘的,所以更改一下混合算法
[Cdst,adst] = [C1,a1] blend [c2,a2] = [C1 * (1-a2) + c2*a2,a1*(1-a2) + a2]
对应Opengl中设置
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
就是源颜色应该乘以alpha之后,再当做预乘alpha混合运算,用这个公式有个前提,C1必须是预乘alpha的,也就是说,用这个公式,一个预乘alpha的颜色 混合 非预乘alpha的颜色,最终出来的也是一个预乘alpha的颜色
由此,可以得出一个结论,由于Opengl内部颜色混合运算使用的预乘alpha颜色混合算法,所以从FrameBuffer或者从显存中读rgba,都应该是预乘了alpha的图片。
如果要将FrameBuffer正确的混合到最终的图像上,首先FrameBuffer应该初始化一个预乘的颜色,通常是(0,0),接着把FrameBuffer按照预乘alpha的混合方式,混合到底图。
Opengl,DirectX内部使用预乘alpha,是由算法决定的,通过设置BlendFunc,虽然可以实现混合非预乘的颜色,但是原理是通过将非预乘alpha的颜色,预乘alpha之后再进行混合,并不是真正的非预乘颜色混合。
另外:不透明颜色,即是非预乘alpha,也是预乘alpha的颜色,两个公式都适用