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:
Shader "ColorSwap/Sprite"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_SwapTex("Color Data", 2D) = "transparent" {}
_Color("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap("Pixel snap", Float) = 0
[HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
v2f vert(appdata_t IN)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(IN);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
#ifdef UNITY_INSTANCING_ENABLED
IN.vertex.xy *= _Flip.xy;
#endif
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color * _RendererColor;
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap(OUT.vertex);
#endif
return OUT;
}
sampler2D _SwapTex;
fixed4 frag(v2f IN) : SV_Target
{
fixed4 c = SampleSpriteTexture(IN.texcoord);
fixed4 swapCol = tex2D(_SwapTex, float2(c.x, 0));
fixed4 final = lerp(c, swapCol, swapCol.a);
final.a = c.a * IN.color.a;
final.rgb *= final.a * IN.color.rgb;
return final;
}
ENDCG
}
}
}
Here is the UI Image version of the color swap shader:
Shader "ColorSwap/Image"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
_SwapTex("Color Data", 2D) = "transparent" {}
_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
_ColorMask("Color Mask", Float) = 15
[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 Off
Lighting Off
ZWrite Off
ZTest[unity_GUIZTestMode]
Blend One OneMinusSrcAlpha
ColorMask[_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ 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;
half4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
sampler2D _SwapTex;
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 /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _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
{
fixed4 texColor = tex2D(_MainTex, IN.texcoord);
fixed4 swapColor = tex2D(_SwapTex, float2(texColor.r, 0));
fixed4 color = IN.color * (lerp(texColor, swapColor, swapColor.a) + _TextureSampleAdd);
color.a = texColor.a;
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip(color.a - 0.001);
#endif
color.rgb *= color.a;
return color;
}
ENDCG
}
}
}
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:
[RequireComponent(typeof(SpriteRenderer))]
public class ColorSprite : MonoBehaviour{
private SpriteRenderer mSpriteRenderer;
private Texture2D mColorSwapTex;
private Color[] mSpriteColors;
private void Awake(){
mSpriteRenderer = this.GetComponent<SpriteRenderer>();
InitColorSwapTex();
}
public void Recolor(Chicken target){
List<int> R_KEY_VALUES = new List<int> { 240, 248, 252 };
List<Color> TARGET_COLORS = new List<Color>
{
target.colorTheme.skinColor,
target.colorTheme.cluckColor,
target.colorTheme.accentColor
};
for(int i = 0; i < R_KEY_VALUES.Count; i++){
SwapColor(R_KEY_VALUES[i], TARGET_COLORS[i]);
}
mColorSwapTex.Apply();
}
private void InitColorSwapTex(){
Texture2D colorSwapTex = new Texture2D(256, 1, TextureFormat.RGBA32, false, false);
colorSwapTex.filterMode = FilterMode.Point;
for (int i = 0; i < colorSwapTex.width; ++i)
colorSwapTex.SetPixel(i, 0, new Color(0.0f, 0.0f, 0.0f, 0.0f));
colorSwapTex.Apply();
mSpriteRenderer.material.SetTexture("_SwapTex", colorSwapTex);
mSpriteColors = new Color[colorSwapTex.width];
mColorSwapTex = colorSwapTex;
}
private void SwapColor(int val, Color color){
mSpriteColors[(int)val] = color;
mColorSwapTex.SetPixel((int)val, 0, color);
}
}
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

