Unity截屏或Render Texture渲染后的图片透明物体出错的问题及解决方法

发现问题

  刚刚接触一个需要截屏分享的项目,但Unity那边一直反馈透明物体截图会错误,有透明物体的地方不管该物体后面有没有其他物体,截屏出来的图片都会变透明掉了。

寻找原因

  我使用的截屏方法如下:

    private IEnumerator ScreenShot()
    {
        yield return new WaitForEndOfFrame();
        Rect rect = new Rect(0, 0, (int)Screen.width, (int)Screen.height);
        Texture2D screenShot = new Texture2D((int)Screen.width, (int)Screen.height, TextureFormat.ARGB32, false);
        screenShot.ReadPixels(rect, 0, 0);
        screenShot.Apply();
        byte[] bytes = screenShot.EncodeToTGA();
        string filename = Application.dataPath + "/Screenshot.tga";
        System.IO.File.WriteAllBytes(filename, bytes);
        Debug.Log("截图成功了");
    }

  刚开始我以为是我的截屏方法出了问题,我百度了很多的截屏方法也是出现这个问题,也尝试了使用Unity的PackageManager自带的Recorder截屏也是出现这样的问题,那说明问题不是出在于截屏的方法,极有可能是shader的问题。
  最后尝试使用Unity默认的StandardShader,通过多次测试发现并不是所有的透明物体都会出现这个问题,Transparent显示是正常的,Fade显示是有问题的,自定义的shader一般都会出现问题。
  下面的这张图就是在Unity测试的,左边是在Unity的Game视图,右边是截屏保存下来的图

Unity截屏或Render Texture渲染后的图片透明物体出错的问题及解决方法
  通过对比发现,这三种RenderingMode的区别在于:
    Opaque是不透明混合(Blend One Zero)
    Transparent是预乘透明度混合(Blend One OneMinusSrcAlpha)
    Fade是传统透明度混合(Blend SrcAlpha OneMinusDstAlpha)

  这三种RenderingMode的意思见下表(可以参考我另一篇文章"【Unity Shader入门】6、Blend-混合",里面有详细的介绍)

  假设该材质的颜色为RmGmBmAm 屏幕颜色为RsGsBsAs

RenderingMode Blend 最终输出
Opaque Blend One Zero outColor = RmGmBmAm
Transparent Blend One OneMinusSrcAlpha outColor = RmGmBmAm + RsGsBsAs * (1 - Am)
Fade Blend SrcAlpha OneMinusDstAlpha outColor = RmGmBmAm * Am + RsGsBsAs * (1 - Am)

  那为什么Transparent显示是正常的,Fade显示是有问题的呢?
  原来,通过上面的公式可以算出:
    Transparent的模式下,输出的A通道值 outColor_A = Am + As * (1 - Am),假如原来屏幕的颜色A值是1的话,那 outColor_A = 1;其实不难发现,这个就是PS的不透明度叠加的A通道的算法,所以就会显示正常了
    Fade的模式下,输出的A通道值 outColor_A = Am * Am + As * (1 - Am),假如原来屏幕的颜色A值是1的话,那 outColor_A = Am * Am + (1 - Am) ;很明显,如果材质的A通道小于1,则outColor_A也会小于1,所以就会导致出现A通道错误的效果。

其实Transparent模式就是PS的不透明度叠加的A通道的算法;Fade模式就是PS的不透明度叠加的RGB通道的算法

如果知道是这个问题就容易解决了

解决问题

  在写shader的时候就得注意一下,如果是直接使用Transparent模式的话截图是没问题的;如果使用Fade模式的话A通道的不透明度混合需要单独区分开来

Shader "Unlit/BlendTest"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            //这是Fade模式的,Blend SrcAlpha OneMinusDstAlpha
            Blend SrcAlpha OneMinusDstAlpha,One OneMinusSrcAlpha
            //这是Transparent模式的,是没问题的,Blend One OneMinusSrcAlpha
            //Blend One OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
            };
            uniform float4 _Color;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = _Color;
                return col;
            }
            ENDCG
        }
    }
}

  那问题来了,如果是不透明材质(关闭混合),材质的A通道又被调了不是1呢?
  这样的话屏幕的A通道值就会被该材质的A通道值直接替换,所以截出来的图片还是会变透明的,但是Unity默认的shader没有这个问题,看一下Unity内置的shader的源码就不难发现:
Unity截屏或Render Texture渲染后的图片透明物体出错的问题及解决方法
Unity截屏或Render Texture渲染后的图片透明物体出错的问题及解决方法Unity截屏或Render Texture渲染后的图片透明物体出错的问题及解决方法
  如果不开启_ALPHABLEND_ON和_ALPHAPREMULTIPLY_ON,则输出的A通道强制为1,这样截屏就不会出现问题了。
  所以我们在写shader的时候也可以这样实现,如果是不透明shader,则强制A通道输出为1。

上一篇:留个档,Unity下,利用TexturePacker命令行自动化创建图集


下一篇:Unity中实现自己的圆形Image组件