Problems and solutions of transparent objects in pictures rendered by Unity screenshot or Render Texture

discover problems

    I just came into contact with a project that needs screenshots to share, but Unity has been feedback that screenshots of transparent objects will be wrong. Where there are transparent objects, no matter whether there are other objects behind the object, the screenshot will become transparent.

Find the reason

  the screen capture method I use is as follows:

    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("Screenshot succeeded");
    }

    at first, I thought there was a problem with my screen capture method. I Baidu many screen capture methods. This problem also occurred. I also tried to use the Recorder provided by Unity's PackageManager. This shows that the problem is not the screen capture method, but probably the shader.
  finally, try to use the default standard shader of Unity. Through multiple tests, it is found that not all transparent objects will have this problem. Transparent display is normal, Fade display is problematic, and custom shaders generally have problems.
    the following figure is tested in Unity. On the left is the Game view of Unity, and on the right is the screenshot saved


  through comparison, it is found that the differences between the three renderingmodes are:
     Opaque is blend one zero
     Transparent is the premultiplied transparency blend (blend one oneminussrcaalpha)
     Fade is a traditional transparency blend (blend srclapha oneminusdstalpha)

  the meanings of these three rendering modes are shown in the table below (you can refer to another article of mine)“ [introduction to Unity Shader] 6. Blend blend ", which has a detailed introduction)

  suppose the color of the material is RmGmBmAm and the screen color is RsGsBsAs

RenderingModeBlendFinal output
OpaqueBlend One ZerooutColor = RmGmBmAm
TransparentBlend One OneMinusSrcAlphaoutColor = RmGmBmAm + RsGsBsAs * (1 - Am)
FadeBlend SrcAlpha OneMinusDstAlphaoutColor = RmGmBmAm * Am + RsGsBsAs * (1 - Am)

  then why is the Transparent display normal and the Fade display problematic?
   originally, it can be calculated through the above formula:
     in Transparent mode, the output channel a value outColor_A = Am + As * (1 - Am). If the color a value of the original screen is 1, it is outColor_A = 1; In fact, it is not difficult to find that this is the algorithm of channel a superimposed by PS opacity, so it will display normally
      in Fade mode, the output channel a value outColor_A = Am * Am + As * (1 - Am). If the color a value of the original screen is 1, it is outColor_A = Am * Am + (1 - Am) ; Obviously, if the a channel of the material is less than 1, the outColor_A will also be less than 1, which will lead to the effect of channel a error.

In fact, Transparent mode is the algorithm of channel A superimposed by PS opacity; Fade mode is the algorithm of RGB channel superimposed by PS opacity

It would be easy to solve the problem if we knew it

solve the problem

    when writing the shader, you should pay attention to that if the Transparent mode is directly used, the screenshot is OK; If Fade mode is used, the opacity mixing of channel A needs to be separated separately

Shader "Unlit/BlendTest"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            //This is Fade mode, blend srcaalpha oneminusdstalpha
            Blend SrcAlpha OneMinusDstAlpha,One OneMinusSrcAlpha
            //This is in Transparent mode. It's OK. Blend one oneminussrcaalpha
            //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
        }
    }
}

  that's the problem. If it's an opaque material (off blending), the A channel of the material is adjusted again. Isn't it 1?
    in this way, the channel A value of the screen will be directly replaced by the channel A value of the material, so the cut image will still become transparent, but the default shader of Unity does not have this problem. It is not difficult to find out by looking at the source code of the built-in shader of Unity:


  if not_ ALPHABLEND_ON and_ ALPHAPREMULTIPLY_ON, the output channel A is forced to be 1, so there will be no problem with the screenshot.
    therefore, we can also implement this when writing the shader. If it is an opaque shader, the output of channel A is forced to be 1.

Keywords: C# Unity Shader UnityShader

Added by Gordonator on Wed, 05 Jan 2022 13:22:06 +0200