在Unity渲染帧导出为透明PNG的思路
发表于2018-10-25
最近碰到一个需求,是将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种选择:
对于操作符,OpenGL可以进行5种操作,对于乘以参数后的Cs与Cd进行Cs+Cd,Cs-Cd,Cd-Cs,取最小,取最大。
在Unity中可以用 Blendop命令进行设置:
例如:Blendop Sub
另外还有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空间中不同物体相互遮挡的情况。
来自:https://blog.csdn.net/liu_if_else/article/details/80148190