【渲染教程】解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

20 十二月, 2014
262
2

昨天我录了一期视频讲解决模型高亮边缘锯齿问题的。今天在群里跟朋友们讨论,结果有人丢出一个文档说是讲的内容有类似。我打开一看,我去,这文档内容写的确实赞,内容深刻,讲解深入浅出,鞭辟入里;而且有一种莫名的熟悉感。结果看到最后的插图我才发现,这篇文章不正是我自己写的吗?连发在哪里都不记得了。这感觉实在奇妙。即熟悉又陌生。不过话说回来,这些年我还是鼓捣出不少很有质量的知识分享的。这篇文章的内容确实很赞,所以再发到博客里,欢迎遇到类似问题的朋友查看~

  文章的内容主要讲述的是:当年将自己渲染的带alpha通道的透明图像,导入ps中去处理的时候,有时候会多出一个像素的黑边或者白边的问题。虽然可以通过一些调整边缘,或者缩小alpha范围来强行解决,但实际上这些解决方法都是不正确的。本文就带你了解在ps中解决这类问题的最正确的做法,并且给你完全阐述清楚问题的本质,让你能做到举一反三,再遇到相似的问题再也不怕了。
  看你朋友还在缩小选取范围来解决问题,果断上去告诉他,你这样做是不对的,给他演示正确的操作,告诉他影藏在背后的本质。完了以后千万不要跟他说,这么牛逼的知识是从戴老师的博客上学来的。恩恩。

像素、抗锯齿的基本概念:

  首先是黑边白边产生的原因——AA抗锯齿,要明确我们渲染出来的图都是位图,在位图中只有完全水平或者垂直的直线才有可能是所有像素都是100%不透明的,任何斜线都必然带有很多透明像素才能让视觉效果看着像斜线。这就是抗锯齿。

 解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

 从小图看的话,就是黑色的直线。

 

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析  从大图看的话,实则斜线的边缘很多黑色都是半透明的。

  因为每个像素都是一个方格,而且并不是无限小,你想象一根有粗细的绳子放在一片小格子上,那么有的格子会被绳子完全沾满,有的可能占了一半,有的可能占了三分之一。那么占满的格子就显示纯黑色,占了一半的格子就显示50%黑,占了三分之一的格子就显示33%黑。从近处看是很多像素方格,但是放远了看,就很像一根斜线了。这是位图对图形的一种近似描述方式。而轮廓线边缘这种像素呈半透明现象要做抗锯齿(anti-aliasing)。叫这个名字的原因是,如果边缘轮廓像素都是纯黑色的话,图像看起来就会有明显的锯齿感!

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析  当我们在三维软件里渲染某一个物体或者角色,并且这个物体或者角色没有占满整个画面的时候。我们就会面临这个轮廓边缘的问题。同样是由于抗锯齿,渲染的物体的边缘像素都是半透明的。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

alpha的两种类型,直通和预乘:

alpha的图片有两种计算方法,一种叫做直通alphastraight alpha),一种叫做预乘alphapremultiplied alpha)。这两种类型在AE软件里是市场可以看到的,当你导入一张带通道的图片,比如说TGAAE就会问你,这张图的alpha是怎么来的,是直通类型还是预乘类型。

这两种类型的唯一区别在于,直通alpha图片保留最原本的RGB数值;而预乘alpha,是原本的RGB信息乘以alpha的数值以后得到的结果(预乘意思就是预先乘以alpha)。

005HE0Kugy6Oqqhjjlz3d&690

  我在三维软件中渲染了一个透明的球。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

  它的alpha通道明显是这样的。
 但其实这张图片原本并不是你看到的那样,它其实应该是下图所示,你看到的球里面多出来的颜色,是场景本身的背景。过程是这样的:本来完好的一张图,前景和背景都有,你分开渲染的时候,前景部分使用alpha通道抠除,就会得到透明效果,而没有被alpha通道抠除之前的效果呢,就是下图这样的。这两种状态呢,其实就是对应的我们alpha通道的两种计算方式:直通和预乘。
005HE0Kugy6Oqqho05Ded&690

  这张图其实就是一张直通类型的图片,它其实就是我们之前透明的预乘图片,除以alpha值得到的结果,这一个除法的步骤,相当于预乘的原图乘以alpha的逆运算,抵消掉了,就可以得到原始的可以看到背景的图片了;图中黑色区域没有原先的背景,是由于我们这张直通的图片,是通过预乘的图片除以alpha通道的值得来的,因为纯透的地方alpha值为0,任何数除以0都得到无限大,所以这些地方原本的信息已经丢失了,只好显示黑色。

所以你会发现,直通和预乘,就是两个逆运算的关系。你原图不用alpha去抠,就是直通的效果,乘以alpha以后就是预乘的效果,预乘的效果除以alpha又得到直通的效果。这就是直通和预乘的关系。

那么定义这两种alpha的计算方式又有啥意义呢?且慢慢道来。

直通和预乘的计算方式:

如果我们要把它合成到一个另外的背景上面去,我们把这层本身叫做A,下面的背景层叫做B
那么在我们最最熟悉的ps里的透明度算法就是这样的:result(结果色)=A*alphaA+B*1-alphaA)。这个公式就像是在算计两个图层的贡献值一样,当alpha为白的时候,则结果色全部都来自图层A,背景被掏空;当alpha为黑的时候,则结果色全部来自图层B,前景完全消失,只看到背景;当alpha0.5的时候,则前景和背景各贡献一半,则看起来就是半透明的。
当你有照片AB,你把A放在B的上面,把A图层的透明度往下调的时候,就相当于启用上面的公式了,透明度为100%,完全显示A照片,透明度为50%,各显示一半,透明度为0%,则完全显示B照片。因为这个操作实在是太熟悉了,所以很多人会忽略在调节透明度的过程中,A图层的透明度在下降,同时B图层的透明度也在上升。在A图层的透明度为100%的时候,B图层等于透明度是0%的,所以你才在最终结果里只看到A图层。

然而,三维软件里面渲染出来的图片,对于alpha的理解是使用的预乘类型。是另一种算法:
result=A+B*1-alpha
这个本质上还是跟上面说的透明度变化公式是一致的。虽然这里的A没有乘以A图层本身的alpha,但是你还记得,预乘类型的图片,已经相当于在原始图片上乘以一个alpha了。所以最终result A原始(或者直通)*alpha +B*1alpha)。

这么说来,你会感觉,预乘就是为什么会有预乘这种奇怪的算法?因为图片先乘了以后,再合成的时候就少算一个步骤,速度会快。这应该是当时开发的时候的思路。

PS中渲染输出黑白边解决方法:

说回图片黑边白边的问题。当你把渲染好的图片导入ps的时候,比如说你用的TGA,那么,你会有一个背景为黑色或者白色的实心图片,顺带一个alpha通道,你要把这个图合到背景上去,你就会用alpha把图抠了,再放到背景上去。这时候图片就会有黑边,除非你背景是黑色看不出来。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

原因是这样的,你导入ps的图片本身是这样的。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

这是三维软件里直接渲染出来的图,是一种预乘类型的图。也就是说,这图已经被alpha乘过了,你就可以理解成已经被alpha扣过了。但是ps这个软件吧,它理解不了这个事情,它以为世界上所有的图片都是直通类型的,就是没有过被alpha扣过的图。也就是说,它以为这图本身的背景就是黑色的。而其实这个图是有一个黄色背景的。

然后,你在ps里面做的事情是用alpha通道去扣这张图,而这张图原本其实是一张预乘的图。或者说,这张图已经被alpha扣过一次了,那么你再去扣一次,会让很多地方看起来比从前更黑,于是黑边就这么产生了。(在图像边缘,有抗锯齿的地方,抗锯齿就是透明,我就不放大看了。)

而在ps里面想要解决的方案是,把一张直通的图导进去。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

再用alpha去抠,才能得到正确的结果。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

mental ray里渲染得到直通图的方法是勾除premultiplyvray里无法实现。

PS中消除黑白边的另外一个方法:

这一段内容呢,在提供方法的同时,更能够加深你对alpha通道的理解。讲得非常棒。

  方法是把背景层给抠一个洞,是用的是前景层alpha的反向。前景层是用addlinear dodge线性增加)模式叠加在背景层上,也是可以得到完美的效果的。

解决三维出图黑白边缘溢出问题:直通(straight)与预乘(premult)alpha剖析

  原理是这样的,前景层我么用的是一张三维软件里面渲染出来,默认预乘了的图。就相当于是A*alphaA,背景层原本是B,我们要得到正确的结果就要往公式上靠:
result=A*alphaA+B*(1-alphaA)

ps里面,正常叠加模式的公式其实是

result=A*alphaA+B*(1-alphaA)

我们一般的做法,把前景层用通道抠一次,直接用normal叠在背景上,得到的结果其实是:

result=A*alphaA*alphaA+B*(1-alphaA)
因为前景层本身就是A*alphaA;背景层是B

add叠加模式的公式是:
result=A+B
如果直接把前景层用add模式叠在背景上,得到的结果是:
result=A*alphaA+B
视觉结果是过亮的。

为了得到正确的结果,我们需要手动把背景层反抠一个洞,模拟ps在做透明度合成时背后发生的事情,才能得到正确的结果。
当使用A图层的alpha的反向去抠B图层,则B图层就变成了B*(1-alphaA)
最后的结果就变成:
result=A*alphaA+B*(1-alphaA)
这就是上图我在ps里做的事情,结果是非常正确的,只不过前景层很不好移动位置。因为要同时移动前景层和背景层的反向alpha(在我上面的图中就是背景层的遮罩),我还真不知道怎么能很方便地一起移动他们。

在AE及Nuke中解决alpha的问题:

因为ps它本身不是一个针对做三维合成用户的后期软件,所以在面对alpha问题的时候,能力很弱,解决起来很麻烦。但是在真正的能做合成的后期软件中,解决起来是很方便的。

使用AE导入TGA文件,选择预乘类型,背景色为你渲染的时候背景的颜色。问题就完美解决了。(注意,在分层渲染,分离前景的时候,要保证背景是纯色,尽量是纯黑,要是你垫在一个五颜六色的背景上渲染,之后又用alpha去抠,会抠不干净

nuke导进去就是好的,默认预乘。

最后,这篇文章相当于是讲解了最基本的alpha的一些概念。正好,看懂了以后,可以比较好地理解我昨天录的视频,来解决怎么消除模型边缘受强光照射时的奇怪锯齿问题。