在Unity渲染帧导出为透明PNG的思路

最近碰到一个需求,是将Unity内的一些特效进行截图,导出带透明度的png用于视频制作,回顾了以下以前研究过的混融相关的问题,也学到了一些新的技巧。

(图1:OpenGL渲染管线)

在OpenGL渲染管线中,对于透明相关的处理是在管线后期进行的。由于不同物体是按照顺序渲染的,(例如一般会根据物体与相机的距离由远至近渲染) 对透明的处理过程,混融blending,实际上就是shader输出颜色与此颜色所在像素位置已有颜色的合并过程。

假如将当前要处理的像素的颜色称为Source Color,简称Cs,将Cs(r,g,b,a)的alpha值称为Source alpha,简称srcA,此像素已有颜色Color Destination简称为Cd,那么,混融后的颜色Color,简称C为:

C=Cs * srcA + Cd * (1-srcA)       (公式1)

以上既是Unity中最标准的混融模式“Blend SrcAlpha OneMinusSrcAlpha”中的计算公式。此公式实际为OpenGL混融方程中的其中一个版本,在OpenGL中:

C=Cs*srcfactor 操作符 Cd*dstfactor (公式2)

OpenGL对srcfactor,dstfactor提供了19个可选参数,例如上文中的source alpha(Cs.a),也可以是destination alpha(Cd.a),或者是常数0或1,甚至还可以是一个固定rgb颜色值。

Unity对srcfactor,distractor提供了10种选择:

   
One The value of one - use this to let either the source or the destination color come through fully.
Zero The value zero - use this to remove either the source or the destination values.
SrcColor The value of this stage is multiplied by the source color value.
SrcAlpha The value of this stage is multiplied by the source alpha value.
DstColor The value of this stage is multiplied by frame buffer source color value.
DstAlpha The value of this stage is multiplied by frame buffer source alpha value.
OneMinusSrcColor The value of this stage is multiplied by (1 - source color).
OneMinusSrcAlpha The value of this stage is multiplied by (1 - source alpha).
OneMinusDstColor The value of this stage is multiplied by (1 - destination color).
OneMinusDstAlpha

The value of this stage is multiplied by (1 - destination alpha) .

对于操作符,OpenGL可以进行5种操作,对于乘以参数后的Cs与Cd进行Cs+Cd,Cs-Cd,Cd-Cs,取最小,取最大。

在Unity中可以用 Blendop命令进行设置:

例如:Blendop Sub

   
Add Add source and destination together.
Sub Subtract destination from source.
RevSub Subtract source from destination.
Min Use the smaller of source and destination.
Max Use the larger of source and destination.

另外还有16种DX专用操作符。

虽然混融阶段不是可编程的,但是通过选择不同的参数与操作符,混融也有很大的可操作空间,例如在unity中,不考虑DX专用操作符,可能的混融模式有10*10*5=500种。

 

(图2:Unity中不同混融模式下的一些差异)

Unity Camera的Clear Flags控制着相机在渲染之前如何clear帧缓存,既是上一帧渲染的内容,例如Skybox意味着清除所有内容并渲染skybox,然后才进行接下来的渲染。四种Clear flags可以看做是四种相机的背景图。

既然已知了混融的参数与方程,那么将函数倒转,既能剔除背景将屏幕上渲染的帧缓存变为只携带自身rgb值的类似带有alpha通道的png图片的效果。例如,分析公式1:

C=Cs * srcA + Cd * (1-srcA) 

我们想要的是Cs:

Cs=(C-Cd*(1-srcA))/srcA (公式2)

渲染的物体有可能是多重的,透明度有可能会叠加,因此srcA为未知数。而我们可以控制背景色Cd,通过camera从帧缓存中获取渲染后的颜色C,它们为已知数。

假设将背景透过Camera的ClearFlag设置为全黑色(0001)或全白色(1111),分别进行渲染,设渲染后的颜色分别为Cblack,Cwhite,将这两个变量导入公式2中设一个方程组既能求得srcA:

Cblack=Cs*srcA+000*(1-srcA)=Cs*srcA

Cwhite=Cs*srcA+111*(1-srcA)=Cblack+111*(1-srcA)

代入后:

srcA=1-(Cwhite-Cblack)/111 = 1-(Cwhite-Cblack)

求得了srcA,再通过公式2,即可得出Cs。将所有像素的Cs重新写到一个材质上既能得出当前帧的png透明图。

老外通过这种思路写的实现代码: http://wiki.unity3d.com/index.php/AnimationToPNG

Custom render texture:

另外还有种简单的方法,既是利用custom render texture,将camera的target texture选为该custom render texture,clear flags的alpha channal设为0,然后即时选择“export"(在inspector栏的右上角setting下拉列表中)既能得到png文件。但是这种方法在场景中被渲染物体的shader为透明类型时会发生一些问题,该物体颜色也会被抹掉。另外物体边缘会被一条很窄的背景色包裹。暂时不理解问题的原因。

当前混融机制的局限性:

由于混融公式是依赖于最后渲染的像素的Alpha值,所以像素的渲染循序对混融结果的颜色有绝对性的影响。由于Unity是根据物体的Z值来一个物体一个物体的循序,当两个3D物体相交时会发生一些问题:

Case1:两个半透明的Box,先渲染红色Box再渲染黄色Box的结果,黄Box前方被红Box遮挡的像素没有通过ZTest,被剔除了未进入混融:

Case2:先渲染黄色Box,同上,红色部分像素未通过ZTest:

Case3:case1,2 部分像素无法通过ZTest的问题通过在Shader中关闭ZWrite解决,红先黄后。出现了新的Bug,前方红Box遮挡黄Box的像素区域颜色错误,被渲染成了黄Box遮挡红Box:

此问题的根源就是GPU在混融阶段根本没考虑像素的Z值,而只是简单的把当前像素在帧缓存中已有的颜色作为Dst,当前处理的颜色的作为Src,也既是根据物体渲染的先后顺序来决定混融的结果。此机制无法正确处理在3D空间中不同物体相互遮挡的情况。

————————————————————————————————————————————————————

参考:

AnimationToPNG-- Brad Nelson

OpenGL 编程指南 Chapter4--Khronos group

Unity Manual: ShaderLab Blending -- Unity Technology

维护日志:

2018-5-2:增,改

2018-5-12:改标题。增加Custom Render Texture部分。

2018-9-26:增加“当前混融机制的局限性”

猜你喜欢

转载自blog.csdn.net/liu_if_else/article/details/80148190