前言

在前文透明贴图实现中的AB透混和AD透叠实现代码中,有提到预乘这个名词,根据贴图是否预乘Alpha,代码的混合模式需要设置不同的参数,自定义混合模式中计算RGB通道的方式也会不同,因此这篇文章就详细介绍一下预乘模式这个概念。

 

预乘模式是什么

在三维软件的渲染设置中,出图格式如果选择tga、tif等带有透明通道(Alpha)的图片时,图片格式设置中往往会有一个关于premult的复选框,例如Maya中的Arnold输出tif格式时是这样的。

可以看到默认情况下是未勾选状态,也就是Premult(预乘)模式;如果勾选上就是Unpremult(非预乘)模式,有些软件里会称为Straight(直通)模式,以下为避免混淆,就称为Straight(直通)模式。

两种类型的区别在于RGB通道的内容,直通Alpha图片的RGB通道保存着最原本的RGB数值(透明信息仅存在于Alpha通道中,在显示图片时RGB通道需要乘以Alpha通道才能得到正确的效果);而预乘Alpha图片的RGB通道保存的则是原本的RGB通道乘以Alpha通道的结果(透明信息既存在于Alpha通道中,也乘进了RGB通道中,在显示图片时只展示RGB通道信息即可)。

一句话来说,预乘=预先乘以,原始RGB信息预先乘以Alpha保存进RGB通道;直通=直存通道,直接将原始RGB信息保存进RGB通道。

 

透明通道原理

对于一个半透明的红色图片,使用常见的8位RGBA(取值范围为[0~255,0~255,0~255,0~1])方式存储,则直通Alpha模式存储为[255, 0, 0, 0.5],预乘Alpha模式存储为[128, 0, 0, 0.5]。

直通Alpha转预乘Alpha的方式是RGB通道分别乘以Alpha,反过来预乘Alpha转直通Alpha的方式则是RGB通道分别除以Alpha。

但对于8位深度的图片,乘除Alpha都会造成图片精度损失,想要无损转换预乘Alpha和直通Alpha,最好使用16位或者32位深度图片。

那么如何在屏幕上显示透明效果呢,大多数情况下是给图片一个默认的背景色,透明区域直接显示背景色即可。

 

直通Alpha的显示原理

将一个直通Alpha模式的透明度为A的颜色C显示在背景色B的上面,计算最终混合颜色F公式如下:

F=C.rgb * A + B.rgb * (1 − A)

例如透明度为0.6的红色图片显示在白色背景上是这样的

[255, 0, 0] * 0.6 + [255, 255, 255] * (1 – 0.6) = [255, 102, 102]

这意味着[255, 0, 0, 0.6]和[255, 102, 102, 1.0]在白色背景上的显示效果是相同的。

而在黑色背景上则是这样的

[255, 0, 0] * 0.6 + [0, 0, 0] * (1 – 0.6) = [153, 0, 0]

同样这意味着[255, 0, 0, 0.6]和[153, 0, 0, 1.0]在黑色背景上的显示效果是相同的。

 

预乘Alpha的显示原理

那么再来看一下预乘Alpha模式的透明度为A的颜色C显示在背景色B的上面,计算最终混合颜色F公式如下:

F=C.rgb + B.rgb * (1 − A)

显然因为已经做过预乘,所以RGB通道不需要再乘以Alpha了,计算量会少一些。

此时透明度为0.6的红色图片显示在白色背景上是这样的

[153, 0, 0] + [255, 255, 255] * (1 – 0.6) = [255, 102, 102]

也就是图片中所存的颜色是被Alpha处理过的颜色[153, 0, 0, 0.6]和[255, 102, 102, 1.0]在白色背景上的显示效果是相同的。

而在黑色背景上是这样的

[153, 0, 0] + [0, 0, 0] * (1 – 0.6) = [153, 0, 0]

也就是图片中所存的颜色是被Alpha处理过的颜色[153, 0, 0, 0.6]和[153, 0, 0, 1.0]在黑色背景上的显示效果是相同的。换句话说在黑色背景上显示预乘Alpha图片时无需计算,直接显示RGB通道即可。

 

预乘模式转换

还是以Maya为例,在勾选与未勾选Unpremult Alpha下,渲染窗口的效果是一致的。

Alpha通道内容也一样。

而输出的图片则不同,直通Alpha的图片在Nuke中打开效果是这样的。

预乘的图片在Nuke中打开则是这样:

可以看出预乘Alpha的图片和Maya中效果一致,而直通Alpha的图片的玻璃球体部分则是完全不透明,带有完整的背景信息(原始的RGB信息)。

这是因为Nuke在使用Read节点读取图片时,默认所有图片是预乘Alpha模式,那么如果要读取的图片是直通Alpha模式时应该怎么办呢?

很简单,既然没有预乘Alpha,那就替他乘Alpha。

先通过shuffle节点取出图片的Alpha通道,再乘以直通Alpha图片(merge节点的multiply),得到的就是预乘Alpha效果,与原始预乘Alpha的素材效果一致。

而在偏向平面设计类的软件中例如Photoshop、AfterEffects以及绝大多数的图片查看工具,都默认所有图片为直通模式,在显示图片的时候会将RGB通道乘以Alpha再进行显示,而预乘的图片本身已经乘过一次,这样一来就会导致预乘的图片乘了两次Alpha,本来半透明的区域会更加透明,导致丢失透明区域细节、物体边缘出现黑边等等问题。

当然在这些软件中也是有办法处理预乘图片的,例如在AfterEffects里面就可以通过解释素材的方式设置图片预乘格式。

只不过,专业的事情还是得专业的来比较好,对吧Nuke老弟。

 

透明贴图算法中的使用

现在,回过头来看前文透明贴图实现中的shader代码,就可以理解为什么不同预乘模式的贴图为什么要使用不同的混合模式了吧。

原因就是RGB通道与Alpha通道的乘法放在哪一步进行计算。

要么选择使用预乘Alpha贴图,在贴图中存的就是处理好的颜色,调用贴图时减少计算量,但预乘Alpha贴图的RGB通道中保存的就不再是原始的RGB颜色,如果用到原始RGB颜色就需要额外一张色彩贴图了。

要么选择使用直通Alpha贴图,可以灵活选择使用RGB通道,如果需要使用透明贴图就需要在shader代码中多做一次乘法了。

实际在工作中,拿到的图片素材预乘Alpha和直通Alpha都有,一般来说三维软件输出的渲染图片预乘Alpha比较多,平面设计软件输出的UI素材直通Alpha比较多,需要先确认再使用。

 

纹理过滤中的使用

在对贴图进行旋转缩放等操作时,需要通过纹理过滤(Texture Filtering)处理,这时需要将直通Alpha贴图处理成预乘Alpha贴图再进行计算,否则会造成色彩插值异常。

以常用的 filtering 方式线性插值为例,一个宽2px高1px的图片,左边的像素是红色,右边是绿色10%透明度,如果把这个图片缩放到1×1的大小,那么缩放后1像素的颜色就是左右两个像素线性插值的结果,也就是把两个像素各个通道加起来除以2求平均值。

如果使用直通Alpha的颜色进行插值,那么结果就是:

([255, 0, 0, 1] + [0, 255, 0, 0.1]) * 0.5 = [127, 127, 0, 0.55]

如果使用预乘Alpha的颜色进行插值,那么结果则是:

([255, 0, 0, 1] + [0, 25, 0, 0.1]) * 0.5 = [127, 25, 0, 0.55](此处结果应为[127, 12, 0, 0.55])

上图中的第三个颜色是直通Alpha插值后的结果,第四个颜色是预乘Alpha插值后的结果,显然后者更符合直觉,因为直通Alpha的颜色没有乘以Alpha,导致在线性插值时占了过大的权重。

因此,先处理成预乘Alpha模式,再进行filtering。

 

本文部分转载自图片Alpha预乘的作用,有修改。


他们兴高采烈地,
过着一成不变的生活。

——奈保尔