Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Color Swap Shader for Sprites and UI

Discussion in 'Shaders' started by unitynoob24, Oct 9, 2021.

  1. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    Hey guys!

    I have this color swap shader which allows me to select the R values of any image and then from a helper script pass any colors I want over colors with matching R values on a sprite. It is working perfectly on Windows / Mac / iOS and MOST android devices. For some reason it doesn't work on my Galaxy S10 or my buddies Pixel4.

    It DOES work on my S10 when using different sprites. Which is the real head scratcher. So I'm not sure if it is something to do with the shader itself, or a setting on the imported texture. I also tried including the shaders with the build shaders so they are explicitly included in the build process, but i'm not sure if that makes a difference as it still doesn't work with the sprites I am trying to have get re-colored.

    Here is the Sprite version of the color swap shader:
    Code (csharp):
    1.  
    2. Shader "ColorSwap/Sprite"
    3. {
    4.    Properties
    5.    {
    6.        [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
    7.        _SwapTex("Color Data", 2D) = "transparent" {}
    8.        _Color("Tint", Color) = (1,1,1,1)
    9.        [MaterialToggle] PixelSnap("Pixel snap", Float) = 0
    10.        [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
    11.        [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
    12.        [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
    13.        [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
    14.    }
    15.  
    16.        SubShader
    17.        {
    18.            Tags
    19.            {
    20.                "Queue" = "Transparent"
    21.                "IgnoreProjector" = "True"
    22.                "RenderType" = "Transparent"
    23.                "PreviewType" = "Plane"
    24.                "CanUseSpriteAtlas" = "True"
    25.            }
    26.  
    27.            Cull Off
    28.            Lighting Off
    29.            ZWrite Off
    30.            Blend One OneMinusSrcAlpha
    31.  
    32.            Pass
    33.            {
    34.            CGPROGRAM
    35.                #pragma vertex vert
    36.                #pragma fragment frag
    37.                #pragma target 2.0
    38.                #pragma multi_compile_instancing
    39.                #pragma multi_compile _ PIXELSNAP_ON
    40.                #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    41.                #include "UnitySprites.cginc"
    42.  
    43.                v2f vert(appdata_t IN)
    44.                {
    45.                    v2f OUT;
    46.  
    47.                    UNITY_SETUP_INSTANCE_ID(IN);
    48.                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    49.  
    50.                #ifdef UNITY_INSTANCING_ENABLED
    51.                    IN.vertex.xy *= _Flip.xy;
    52.                #endif
    53.  
    54.                    OUT.vertex = UnityObjectToClipPos(IN.vertex);
    55.                    OUT.texcoord = IN.texcoord;
    56.                    OUT.color = IN.color * _Color * _RendererColor;
    57.  
    58.                    #ifdef PIXELSNAP_ON
    59.                    OUT.vertex = UnityPixelSnap(OUT.vertex);
    60.                    #endif
    61.  
    62.                    return OUT;
    63.                }
    64.  
    65.                sampler2D _SwapTex;
    66.  
    67.                fixed4 frag(v2f IN) : SV_Target
    68.                {
    69.                    fixed4 c = SampleSpriteTexture(IN.texcoord);
    70.                    fixed4 swapCol = tex2D(_SwapTex, float2(c.x, 0));
    71.                    fixed4 final = lerp(c, swapCol, swapCol.a);
    72.                    final.a = c.a * IN.color.a;
    73.                    final.rgb *= final.a * IN.color.rgb;
    74.                    return final;
    75.                }
    76.            ENDCG
    77.            }
    78.        }
    79. }
    80.  
    Here is the UI Image version of the color swap shader:
    Code (csharp):
    1.  
    2. Shader "ColorSwap/Image"
    3. {
    4.    Properties
    5.    {
    6.        [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
    7.        _Color("Tint", Color) = (1,1,1,1)
    8.        _SwapTex("Color Data", 2D) = "transparent" {}
    9.        _StencilComp("Stencil Comparison", Float) = 8
    10.        _Stencil("Stencil ID", Float) = 0
    11.        _StencilOp("Stencil Operation", Float) = 0
    12.        _StencilWriteMask("Stencil Write Mask", Float) = 255
    13.        _StencilReadMask("Stencil Read Mask", Float) = 255
    14.  
    15.        _ColorMask("Color Mask", Float) = 15
    16.  
    17.        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
    18.    }
    19.  
    20.        SubShader
    21.        {
    22.            Tags
    23.            {
    24.                "Queue" = "Transparent"
    25.                "IgnoreProjector" = "True"
    26.                "RenderType" = "Transparent"
    27.                "PreviewType" = "Plane"
    28.                "CanUseSpriteAtlas" = "True"
    29.            }
    30.  
    31.            Stencil
    32.            {
    33.                Ref[_Stencil]
    34.                Comp[_StencilComp]
    35.                Pass[_StencilOp]
    36.                ReadMask[_StencilReadMask]
    37.                WriteMask[_StencilWriteMask]
    38.            }
    39.  
    40.            Cull Off
    41.            Lighting Off
    42.            ZWrite Off
    43.            ZTest[unity_GUIZTestMode]
    44.            Blend One OneMinusSrcAlpha
    45.            ColorMask[_ColorMask]
    46.  
    47.            Pass
    48.            {
    49.                Name "Default"
    50.            CGPROGRAM
    51.                #pragma vertex vert
    52.                #pragma fragment frag
    53.                #pragma target 2.0
    54.  
    55.                #include "UnityCG.cginc"
    56.                #include "UnityUI.cginc"
    57.  
    58.                #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
    59.                #pragma multi_compile_local _ UNITY_UI_ALPHACLIP
    60.  
    61.                struct appdata_t
    62.                {
    63.                    float4 vertex   : POSITION;
    64.                    float4 color    : COLOR;
    65.                    float2 texcoord : TEXCOORD0;
    66.                    UNITY_VERTEX_INPUT_INSTANCE_ID
    67.                };
    68.  
    69.                struct v2f
    70.                {
    71.                    float4 vertex   : SV_POSITION;
    72.                    fixed4 color : COLOR;
    73.                    float2 texcoord  : TEXCOORD0;
    74.                    float4 worldPosition : TEXCOORD1;
    75.                    half4  mask : TEXCOORD2;
    76.                    UNITY_VERTEX_OUTPUT_STEREO
    77.                };
    78.  
    79.                sampler2D _MainTex;
    80.                sampler2D _SwapTex;
    81.                fixed4 _Color;
    82.                fixed4 _TextureSampleAdd;
    83.                float4 _ClipRect;
    84.                float4 _MainTex_ST;
    85.                float _UIMaskSoftnessX;
    86.                float _UIMaskSoftnessY;
    87.  
    88.                v2f vert(appdata_t v)
    89.                {
    90.                    v2f OUT;
    91.                    UNITY_SETUP_INSTANCE_ID(v);
    92.                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    93.                    float4 vPosition = UnityObjectToClipPos(v.vertex);
    94.                    OUT.worldPosition = v.vertex;
    95.                    OUT.vertex = vPosition;
    96.  
    97.                    float2 pixelSize = vPosition.w;
    98.                    pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
    99.  
    100.                    float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    101.                    float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
    102.                    OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
    103.                    OUT.mask = half4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
    104.  
    105.                    OUT.color = v.color * _Color;
    106.                    return OUT;
    107.                }
    108.  
    109.                fixed4 frag(v2f IN) : SV_Target
    110.                {
    111.                    fixed4 texColor = tex2D(_MainTex, IN.texcoord);
    112.                    fixed4 swapColor = tex2D(_SwapTex, float2(texColor.r, 0));
    113.  
    114.                    fixed4 color = IN.color * (lerp(texColor, swapColor, swapColor.a) + _TextureSampleAdd);
    115.                    color.a = texColor.a;
    116.  
    117.                    #ifdef UNITY_UI_CLIP_RECT
    118.                    half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
    119.                    color.a *= m.x * m.y;
    120.                    #endif
    121.  
    122.                    #ifdef UNITY_UI_ALPHACLIP
    123.                    clip(color.a - 0.001);
    124.                    #endif
    125.  
    126.                    color.rgb *= color.a;
    127.  
    128.                    return color;
    129.                }
    130.            ENDCG
    131.            }
    132.        }
    133. }
    134.  
    Here is the basic implementation / helper script that sits on either a sprite renderer or an image to spin up and inject pre-determined colors over the R values of said base sprite:
    Code (csharp):
    1.  
    2. [RequireComponent(typeof(SpriteRenderer))]
    3.  
    4.     public class ColorSprite : MonoBehaviour{
    5.  
    6.         private SpriteRenderer mSpriteRenderer;
    7.         private Texture2D mColorSwapTex;
    8.         private Color[] mSpriteColors;
    9.  
    10.         private void Awake(){
    11.             mSpriteRenderer = this.GetComponent<SpriteRenderer>();
    12.             InitColorSwapTex();
    13.         }
    14.      
    15.         public void Recolor(Chicken target){
    16.        
    17.             List<int> R_KEY_VALUES = new List<int> { 240, 248, 252 };
    18.             List<Color> TARGET_COLORS = new List<Color>
    19.             {
    20.                 target.colorTheme.skinColor,
    21.                 target.colorTheme.cluckColor,
    22.                 target.colorTheme.accentColor
    23.             };
    24.  
    25.             for(int i = 0; i < R_KEY_VALUES.Count; i++){
    26.                 SwapColor(R_KEY_VALUES[i], TARGET_COLORS[i]);
    27.             }
    28.  
    29.             mColorSwapTex.Apply();
    30.  
    31.         }
    32.      
    33.         private void InitColorSwapTex(){
    34.  
    35.             Texture2D colorSwapTex = new Texture2D(256, 1, TextureFormat.RGBA32, false, false);
    36.             colorSwapTex.filterMode = FilterMode.Point;
    37.  
    38.             for (int i = 0; i < colorSwapTex.width; ++i)
    39.                 colorSwapTex.SetPixel(i, 0, new Color(0.0f, 0.0f, 0.0f, 0.0f));
    40.  
    41.             colorSwapTex.Apply();
    42.  
    43.             mSpriteRenderer.material.SetTexture("_SwapTex", colorSwapTex);
    44.             mSpriteColors = new Color[colorSwapTex.width];
    45.             mColorSwapTex = colorSwapTex;
    46.  
    47.         }
    48.  
    49.         private void SwapColor(int val, Color color){
    50.             mSpriteColors[(int)val] = color;
    51.             mColorSwapTex.SetPixel((int)val, 0, color);
    52.         }
    53.     }
    54.  
    Also, attached are images of the import settings that WORK on my S10 and one that does NOT work on my S10.

    Thank you in advance for any direction or tips you guys! @bgolus
     

    Attached Files:

  2. unitynoob24

    unitynoob24

    Joined:
    Dec 27, 2014
    Posts:
    398
    So after much headache and several refactoring sessions, turned out that the shader and my helper class were fine.

    For some reason some android devices were not seeing the proper R channel color values. I'm still not sure why, maybe something with colorspace? Either way, the 'fix' was changing the base spite (where the original R channels had been mapped from) - to greyscale, then generating new R values and now it works on all of my devices.

    Super strange, but I guess not all devices were able to 'see' the exact R color values that the recolor class was looking for so it was just not coloing anything.