Unity’s UGUI is grayed out (TMP, Image, RawImage, Spine animation)

Unity’s UGUI is grayed out (TMP, Image, RawImage, Spine animation)

Introduction

During the development process, I encountered a function that needed to be grayed out, but the only path given was the path to the UI game body. You need to consider TMP, TMP graphics and text mixing, Image, Spine animation, etc. for graying out.
Practice has found that except for TMP, which is a bit special, all other display components that inherit the MaskableGraphic component are modified in the same way.
I searched most of the content on the Internet, but the TMP was not grayed out, so I had to get one myself.

TMP graying

Plain text is grayed out

1. Add a grayed out Shader for TMP Shader. This step only requires a grayed out processing at the end of the target Shader. Post the Shader I added below.
2.Shader code:

// Simplified SDF shader:
// - No Shading Option (bevel / bump / env map)
// - No Glow Option
// - Softness is applied on both sides of the outline

Shader "TextMeshPro/Mobile/Distance Field Grey" {<!-- -->

Properties {<!-- -->
_FaceColor ("Face Color", Color) = (1,1,1,1)
_FaceDilate ("Face Dilate", Range(-1,1)) = 0

_OutlineColor ("Outline Color", Color) = (0,0,0,1)
_OutlineWidth ("Outline Thickness", Range(0,1)) = 0
_OutlineSoftness ("Outline Softness", Range(0,1)) = 0

_UnderlayColor ("Border Color", Color) = (0,0,0,.5)
_UnderlayOffsetX ("Border OffsetX", Range(-1,1)) = 0
_UnderlayOffsetY ("Border OffsetY", Range(-1,1)) = 0
_UnderlayDilate ("Border Dilate", Range(-1,1)) = 0
_UnderlaySoftness ("Border Softness", Range(0,1)) = 0

_WeightNormal ("Weight Normal", float) = 0
_WeightBold ("Weight Bold", float) = .5

_ShaderFlags ("Flags", float) = 0
_ScaleRatioA ("Scale RatioA", float) = 1
_ScaleRatioB ("Scale RatioB", float) = 1
_ScaleRatioC ("Scale RatioC", float) = 1

_MainTex ("Font Atlas", 2D) = "white" {<!-- -->}
_TextureWidth ("Texture Width", float) = 512
_TextureHeight ("Texture Height", float) = 512
_GradientScale ("Gradient Scale", float) = 5
_ScaleX ("Scale X", float) = 1
_ScaleY ("Scale Y", float) = 1
_PerspectiveFilter ("Perspective Correction", Range(0, 1)) = 0.875
_Sharpness ("Sharpness", Range(-1,1)) = 0

_VertexOffsetX ("Vertex OffsetX", float) = 0
_VertexOffsetY ("Vertex OffsetY", float) = 0

_ClipRect ("Clip Rect", vector) = (-32767, -32767, 32767, 32767)
_MaskSoftnessX ("Mask SoftnessX", float) = 0
_MaskSoftnessY ("Mask SoftnessY", float) = 0

_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255

_CullMode ("Cull Mode", Float) = 0
_ColorMask ("Color Mask", Float) = 15
}

SubShader {<!-- -->
Tags
{<!-- -->
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}


Stencil
{<!-- -->
Ref [_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}

Cull[_CullMode]
ZWrite Off
Lighting Off
Fog {<!-- --> Mode Off }
ZTest [unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask [_ColorMask]

Pass {<!-- -->
CGPROGRAM
#pragma vertex VertShader
#pragma fragment PixShader
#pragma shader_feature __ OUTLINE_ON
#pragma shader_feature __ UNDERLAY_ON UNDERLAY_INNER

#pragma multi_compile __ UNITY_UI_CLIP_RECT
#pragma multi_compile __ UNITY_UI_ALPHACLIP

#include "UnityCG.cginc"
#include "UnityUI.cginc"
#include "TMPro_Properties.cginc"

struct vertex_t {<!-- -->
UNITY_VERTEX_INPUT_INSTANCE_ID
float4 vertex : POSITION;
float3 normal : NORMAL;
fixed4 color : COLOR;
float4 texcoord0 : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;
};

struct pixel_t {<!-- -->
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
float4 vertex : SV_POSITION;
fixed4 faceColor : COLOR;
fixed4 outlineColor : COLOR1;
float4 texcoord0 : TEXCOORD0; // Texture UV, Mask UV
half4 param : TEXCOORD1; // Scale(x), BiasIn(y), BiasOut(z), Bias(w)
half4 mask : TEXCOORD2; // Position in clip space(xy), Softness(zw)
#if (UNDERLAY_ON | UNDERLAY_INNER)
float4 texcoord1 : TEXCOORD3; // Texture UV, alpha, reserved
half2 underlayParam : TEXCOORD4; // Scale(x), Bias(y)
#endif
};

float _UIMaskSoftnessX;
        float _UIMaskSoftnessY;

pixel_t VertShader(vertex_t input)
{<!-- -->
pixel_t output;

UNITY_INITIALIZE_OUTPUT(pixel_t, output);
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

float bold = step(input.texcoord0.w, 0);

float4 vert = input.vertex;
vert.x + = _VertexOffsetX;
vert.y + = _VertexOffsetY;
float4 vPosition = UnityObjectToClipPos(vert);

float2 pixelSize = vPosition.w;
pixelSize /= float2(_ScaleX, _ScaleY) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

float scale = rsqrt(dot(pixelSize, pixelSize));
scale *= abs(input.texcoord0.w) * _GradientScale * (_Sharpness + 1);
if(UNITY_MATRIX_P[3][3] == 0) scale = lerp(abs(scale) * (1 - _PerspectiveFilter), scale, abs(dot(UnityObjectToWorldNormal(input.normal.xyz), normalize(WorldSpaceViewDir(vert)) )));

float weight = lerp(_WeightNormal, _WeightBold, bold) / 4.0;
weight = (weight + _FaceDilate) * _ScaleRatioA * 0.5;

float layerScale = scale;

scale /= 1 + (_OutlineSoftness * _ScaleRatioA * scale);
float bias = (0.5 - weight) * scale - 0.5;
float outline = _OutlineWidth * _ScaleRatioA * 0.5 * scale;

float opacity = input.color.a;
#if (UNDERLAY_ON | UNDERLAY_INNER)
opacity = 1.0;
#endif

fixed4 faceColor = fixed4(input.color.rgb, opacity) * _FaceColor;
faceColor.rgb *= faceColor.a;

fixed4 outlineColor = _OutlineColor;
outlineColor.a *= opacity;
outlineColor.rgb *= outlineColor.a;
outlineColor = lerp(faceColor, outlineColor, sqrt(min(1.0, (outline * 2))));

#if (UNDERLAY_ON | UNDERLAY_INNER)
layerScale /= 1 + ((_UnderlaySoftness * _ScaleRatioC) * layerScale);
float layerBias = (.5 - weight) * layerScale - .5 - ((_UnderlayDilate * _ScaleRatioC) * .5 * layerScale);

float x = -(_UnderlayOffsetX * _ScaleRatioC) * _GradientScale / _TextureWidth;
float y = -(_UnderlayOffsetY * _ScaleRatioC) * _GradientScale / _TextureHeight;
float2 layerOffset = float2(x, y);
#endif

// Generate UV for the Masking Texture
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (vert.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);

// Populate structure for pixel shader
output.vertex = vPosition;
output.faceColor = faceColor;
output.outlineColor = outlineColor;
output.texcoord0 = float4(input.texcoord0.x, input.texcoord0.y, maskUV.x, maskUV.y);
output.param = half4(scale, bias - outline, bias + outline, bias);

const half2 maskSoftness = half2(max(_UIMaskSoftnessX, _MaskSoftnessX), max(_UIMaskSoftnessY, _MaskSoftnessY));
output.mask = half4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * maskSoftness + pixelSize.xy));
#if (UNDERLAY_ON || UNDERLAY_INNER)
output.texcoord1 = float4(input.texcoord0 + layerOffset, input.color.a, 0);
output.underlayParam = half2(layerScale, layerBias);
#endif

return output;
}


//PIXEL SHADER
fixed4 PixShader(pixel_t input) : SV_Target
{<!-- -->
UNITY_SETUP_INSTANCE_ID(input);

half d = tex2D(_MainTex, input.texcoord0.xy).a * input.param.x;
half4 c = input.faceColor * saturate(d - input.param.w);

#ifdef OUTLINE_ON
c = lerp(input.outlineColor, input.faceColor, saturate(d - input.param.z));
c *= saturate(d - input.param.y);
#endif

#if UNDERLAY_ON
d = tex2D(_MainTex, input.texcoord1.xy).a * input.underlayParam.x;
c + = float4(_UnderlayColor.rgb * _UnderlayColor.a, _UnderlayColor.a) * saturate(d - input.underlayParam.y) * (1 - c.a);
#endif

#if UNDERLAY_INNER
half sd = saturate(d - input.param.z);
d = tex2D(_MainTex, input.texcoord1.xy).a * input.underlayParam.x;
c + = float4(_UnderlayColor.rgb * _UnderlayColor.a, _UnderlayColor.a) * (1 - saturate(d - input.underlayParam.y)) * sd * (1 - c.a);
#endif

// Alternative implementation to UnityGet2DClipping with support for softness.
#if UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(input.mask.xy)) * input.mask.zw);
c *= m.x * m.y;
#endif

#if (UNDERLAY_ON | UNDERLAY_INNER)
c *= input.texcoord1.z;
#endif

#if UNITY_UI_ALPHACLIP
clip(c.a - 0.001);
#endif

half gray = dot(c.rgb, half3(0.299, 0.587, 0.114));
c.rgb = lerp(c.rgb, half3(grey, grey, grey), c.a);
return c;
}
ENDCG
}
}

CustomEditor "TMPro.EditorUtilities.TMP_SDFShaderGUI"
}

3. Modify TMP_Text and add the following code

 /// <summary>
        /// Is it currently in grayed out state?
        /// </summary>
        private bool _curIsGrey = false;

        /// <summary>
        /// The material before graying out
        /// </summary>
        private Material _oriMaterial = null;
        
        /// <summary>
        /// Control gray status
        /// </summary>
        /// <param name="isGrey"></param>
        public void SetGreyState(bool isGrey)
        {<!-- -->
            if(isGrey)
            {<!-- -->
                _oriMaterial = fontMaterial;
                
                Shader grayShader = Shader.Find("TextMeshPro/Mobile/Distance Field Grey");
                Material grayMaterial = Instantiate(fontMaterial);
                greyMaterial.shader = greyShader;
                fontMaterial = grayMaterial;
            }
            else
            {<!-- -->
                if (_oriMaterial != null)
                {<!-- -->
                    fontMaterial = _oriMaterial;
                }
            }

            _curIsGrey = isGrey;
        }

4. After obtaining the TMP_Text component, call the SetGreyState function to achieve graying.

Greying out images after mixed graphics and text

1. The gray setting in the previous step can only realize the graying of the copywriting in TMP. If there are pictures such as Emoji inserted into the copywriting, it will not be grayed out.
2. Add a new Shader file TMP_Sprite_Grey.shader and add gray processing at the end.

Shader "TextMeshPro/Sprite_Grey"
{<!-- -->
Properties
{<!-- -->
        _MainTex ("Sprite Texture", 2D) = "white" {<!-- -->}
_Color ("Tint", Color) = (1,1,1,1)

_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255

_CullMode ("Cull Mode", Float) = 0
_ColorMask ("Color Mask", Float) = 15
_ClipRect ("Clip Rect", vector) = (-32767, -32767, 32767, 32767)

[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}

SubShader
{<!-- -->
Tags
{<!-- -->
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}

Stencil
{<!-- -->
Ref [_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}

Cull[_CullMode]
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]

Pass
{<!-- -->
            Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment fragment
            #pragma target 2.0

#include "UnityCG.cginc"
#include "UnityUI.cginc"

            #pragma multi_compile __ UNITY_UI_CLIP_RECT
            #pragma multi_compile __ UNITY_UI_ALPHACLIP

struct appdata_t
{<!-- -->
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct v2f
{<!-- -->
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
                UNITY_VERTEX_OUTPUT_STEREO
};

            sampler2D _MainTex;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
            float4 _MainTex_ST;
float _UIMaskSoftnessX;
            float _UIMaskSoftnessY;

            v2f vert(appdata_t v)
{<!-- -->
v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float4 vPosition = UnityObjectToClipPos(v.vertex);
            OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;

            float2 pixelSize = vPosition.w;
                pixelSize /= abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                OUT.mask = half4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));

                OUT.color = v.color * _Color;
return OUT;
}

fixed4 frag(v2f IN) : SV_Target
{<!-- -->
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                #if UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color *= m.x * m.y;
#endif

#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif

                half gray = dot(color.rgb, half3(0.299, 0.587, 0.114));
color.rgb = lerp(color.rgb, half3(grey, grey, grey), color.a);
return color;
}
ENDCG
}
}
}

3. Modify the TMP_SubMeshUI script and add the following code

 /// <summary>
        /// Is it currently in grayed out state?
        /// </summary>
        private bool _curIsGrey = false;

        /// <summary>
        /// The material before graying out
        /// </summary>
        private Material _oriMaterial = null;
        
        /// <summary>
        /// Control gray status
        /// </summary>
        /// <param name="isGrey"></param>
        public void SetGreyState(bool isGrey)
        {<!-- -->
            if(isGrey)
            {<!-- -->
                _oriMaterial = sharedMaterial;
                
                Shader grayShader = Shader.Find("TextMeshPro/Sprite_Grey");
                Material grayMaterial = Instantiate(sharedMaterial);
                greyMaterial.shader = greyShader;
                SetSharedMaterialWhenSetGrey(greyMaterial);
            }
            else
            {<!-- -->
                if (_oriMaterial != null)
                {<!-- -->
                    SetSharedMaterialWhenSetGrey(_oriMaterial);
                }
            }

            _curIsGrey = isGrey;
        }
        
        void SetSharedMaterialWhenSetGrey(Material mat)
        {<!-- -->
            //Debug.Log("*** SetSharedMaterial UI() *** FRAME (" + Time.frameCount + ")");

            // Assign new material.
            m_sharedMaterial = mat;
            m_Material = m_sharedMaterial;
            
            //m_isDefaultMaterial = false;
            //if (mat.GetInstanceID() == m_fontAsset.material.GetInstanceID())
            // m_isDefaultMaterial = true;

            // Compute and Set new padding values for this new material.
            m_padding = GetPaddingForMaterial();

            //SetVerticesDirty();
            SetMaterialDirty();

#if UNITY_EDITOR
            //if (m_sharedMaterial != null)
            // gameObject.name = "TMP SubMesh [" + m_sharedMaterial.name + "]";
#endif
        }

4. Modify the SetSharedMaterial function in TMP_SubMeshUI. The modified code is as follows

 /// <summary>
        /// Method to set the shared material.
        /// </summary>
        /// <param name="mat"></param>
        void SetSharedMaterial(Material mat)
        {<!-- -->
            //Debug.Log("*** SetSharedMaterial UI() *** FRAME (" + Time.frameCount + ")");

            // Assign new material.
            m_sharedMaterial = mat;
            m_Material = m_sharedMaterial;

            if (_curIsGrey)
            {<!-- -->
                SetGreyState(true);
            }

            //m_isDefaultMaterial = false;
            //if (mat.GetInstanceID() == m_fontAsset.material.GetInstanceID())
            // m_isDefaultMaterial = true;

            // Compute and Set new padding values for this new material.
            m_padding = GetPaddingForMaterial();

            //SetVerticesDirty();
            SetMaterialDirty();

            #if UNITY_EDITOR
            //if (m_sharedMaterial != null)
            // gameObject.name = "TMP SubMesh [" + m_sharedMaterial.name + "]";
            #endif
        }

5. After TMP_Text obtains the TMP_SubMeshUI instance in the child node and calls the SetGreyState() function, the image in the TMP copy can be grayed out.

Other grayed out components inherited from the MaskableGraphic component (Image, RawImage, Spine animation)

Detailed implementation

1. Added new gray shader, ImageGrey

Shader "Custom/UIGray"
{<!-- -->
    Properties
    {<!-- -->
        [PerRendererData]_MainTex("MainTex", 2D) = "white" {<!-- -->}
    }
    SubShader
    {<!-- -->
        Tags
        {<!-- -->
            "Queue" = "Transparent"
            "IngnoreProjector" = "True"
            "RenderType" = "Transparent"
        }

        Pass
        {<!-- -->
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert;
            #pragma fragment fragment;

            sampler2D _MainTex;
            float4 _MainTex_ST; // The texture does not need to be edited, and ST does not need to be configured.

            struct a2v
            {<!-- -->
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {<!-- -->
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(a2v v)
            {<!-- -->
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);
                f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                return f;
            }

            fixed4 frag(v2f f) : SV_Target
            {<!-- -->
                // Gray out
                fixed3 grayRGB = dot(tex2D(_MainTex, f.uv), fixed3(0.299, 0.587, 0.114));
                // transparency
                fixed grayA = tex2D(_MainTex, f.uv).a;
                return fixed4(grayRGB, grayA);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

2. Create a new corresponding graying script. The key code is as follows

 private Material _oriImageMaterial;
    private bool SetMaskableGraphicGreyState(bool isGrey)
    {<!-- -->
        Shader shader = Shader.Find("Custom/UIGray");
        _imageGreyMaterial = new Material(shader);
        _maskableGraphic = GetComponent<MaskableGraphic>();
        if (_maskableGraphic == null || _imageGreyMaterial == null)
        {<!-- -->
            return false;
        }

        if(isGrey)
        {<!-- -->
            if (_oriImageMaterial == null)
            {<!-- -->
                _oriImageMaterial = _maskableGraphic.material;
            }
            _maskableGraphic.material = _imageGreyMaterial;
            _maskableGraphic.SetAllDirty();
        }
        else
        {<!-- -->
            _maskableGraphic.material = _oriImageMaterial;
            _maskableGraphic.SetAllDirty();
        }
        return true;
    }

3. Finally, call this function in the script to achieve the graying effect.