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

Best / Easiest way to change color of certain pixels in a single Sprite?

Discussion in '2D' started by CarterG81, Jan 17, 2014.

  1. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I am used to dealing in just 2D and with much lower level programming, so forgive my newbie Unity question.

    I have one 40x40 pixel sprite which is used for all characters, but the player gets to choose the color of their skin, the color of their shirt, and color of their hair. This 40x40 sprite is very simple and pixelated.

    So the texture looks funny, with pure red/green/blue/yellow/etc. for the dyeable areas.
    In C#, I simply took the image from memory, made a copy of it, dyed the character to the correct colors, and then displayed the image as a sprite.

    In this way, I could have multiple characters who all look different. The reason I copied the images before changing them was so that ALL characters wouldn't change when ONE was colored a certain way. All characters used the same sprite, but different coloring of that one sprite.


    I would appreciate any advice as to what is the best or easiest way to do this would be. As I said before, I simply did everything through simple getPixel(), setPixel() functions and a few loops when dealing with the image in memory.


    All of it is done before gameplay begins, or before a character is instantiated. So the characters never change color except when they're being created.
     
    Raial and blox5000 like this.
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    You can do that basically the same way in Unity. Just remember to make the texture readable first (change texture type from Sprite to Advanced to access that bit).

    --Eric
     
    blox5000 likes this.
  3. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Oh, awesome! :)
    Thank you, I will be digging in the API then whenever I get to that point.
     
    blox5000 likes this.
  4. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Mark the texture `isReadable` and then you can access it and change it.

    Another way to do this would be to make a shader which takes various color inputs and recolors it on the fly... but note you'll get another draw call every time you have a unique shader or texture.
     
  5. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    If I mark it as "Read/Write Enabled", then won't it change the actual texture file in the asset folder? Permanently change it?

    All characters use the same sprite sheet (Sprite, Sprite Mode: Multiple) but each individual character in the game needs to have different colors for certain pixels.


    Something like, the original image has pure green (Color 0,255,0) for the hair pixels.
    When instantiated, the character's hair color is given as a parameter during instantiation, with a result like...

    Character 1 = brown hair
    Character 2 = blonde hair
    Character 3 = red hair

    All sharing the exact same texture2D sprite sheet, "Assets\Graphics\Character_SpriteSheet.png"


    Would it be better to just use a shader? Otherwise, wouldn't I have to create a unique texture anyway- so either way I have to do an extra draw call? Or can I have character prefabs which each have the Character_SpriteSheet.png unity sprite, and with read/write enabled I can edit one character without also changing the colors on another instantiated prefab, which uses the exact same unity sprite?
     
    Last edited: Jan 19, 2014
  6. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    No, you need read/write enabled in order to read the texture in RAM. Otherwise it only exists in VRAM and you have no access to it.

    A shader is more complicated and has to recompute the texture every frame, which granted may be very fast on the GPU, but nevertheless it's extra work.

    You would of course edit instances of the texture for each character.

    --Eric
     
    tobad likes this.
  7. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Ah yes, this I discovered as I messed around.

    Thank you.

    I have been attempting this all night, but so far have little to show for it.
     
  8. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    To anyone who is interested and finds this for reference, here is the solution:


    Code (csharp):
    1.  
    2. //CopiedTexture is the original Texture  which you want to copy.
    3. public Texture2D CopyTexture2D(Texture2D copiedTexture)
    4.     {
    5.                //Create a new Texture2D, which will be the copy.
    6.         Texture2D texture = new Texture2D(copiedTexture.width, copiedTexture.height);
    7.                //Choose your filtermode and wrapmode here.
    8.         texture.filterMode = FilterMode.Point;
    9.         texture.wrapMode = TextureWrapMode.Clamp;
    10.  
    11.         int y = 0;
    12.         while (y < texture.height)
    13.         {
    14.             int x = 0;
    15.             while (x < texture.width)
    16.             {
    17.                                //INSERT YOUR LOGIC HERE
    18.                 if(copiedTexture.GetPixel(x,y) == Color.green)
    19.                 {
    20.                                        //This line of code and if statement, turn Green pixels into Red pixels.
    21.                     texture.SetPixel(x, y, Color.red);
    22.                 }
    23.                 else
    24.                 {
    25.                                //This line of code is REQUIRED. Do NOT delete it. This is what copies the image as it was, without any change.
    26.                 texture.SetPixel(x, y, copiedTexture.GetPixel(x,y));
    27.                 }
    28.                 ++x;
    29.             }
    30.             ++y;
    31.         }
    32.                 //Name the texture, if you want.
    33.         texture.name = (Species+Gender+"_SpriteSheet");
    34.  
    35.                //This finalizes it. If you want to edit it still, do it before you finish with .Apply(). Do NOT expect to edit the image after you have applied. It did NOT work for me to edit it after this function.
    36.         texture.Apply();
    37.  
    38. //Return the variable, so you have it to assign to a permanent variable and so you can use it.
    39.         return texture;
    40.     }
    41.  
    42.     public void UpdateCharacterTexture()
    43.     {
    44. //This calls the copy texture function, and copies it. The variable characterTextures2D is a Texture2D which is now the returned newly copied Texture2D.
    45.         characterTexture2D = CopyTexture2D(gameObject.GetComponent<SpriteRenderer> ().sprite.texture);
    46.  
    47. //Get your SpriteRenderer, get the name of the old sprite,  create a new sprite, name the sprite the old name, and then update the material. If you have multiple sprites, you will want to do this in a loop- which I will post later in another post.
    48.         SpriteRenderer sr = GetComponent<SpriteRenderer>();
    49.         string tempName = sr.sprite.name;
    50.         sr.sprite = Sprite.Create (characterTexture2D, sr.sprite.rect, new Vector2(0,1));
    51.         sr.sprite.name = tempName;
    52.  
    53.         sr.material.mainTexture = characterTexture2D;
    54.         sr.material.shader = Shader.Find ("Sprites/Transparent Unlit");
    55.  
    56.     }
    57.  

    CopyTexture2D will take a any Texture2D (including a Sprite's SpriteSheet / Texture2D) and then create a copy of it. If you change the new copy, it will NOT effect the old original Texture2D. This was required for me to both change the pixel's colors AND change the Texture2D without effecting ALL characters who use that Texture2D.

    The Sprite with SpriteMode: Multiple, needs to be turned from a "Texture Type: Sprite" into a "Texture Type: Advanced and have Read/Write ENABLED.

    If you want to change the FilterMode or WrapMode, do so in CopyTexture2D, as I have mine set as "Point" and "Clamp".


    To change a pixel's color , go to the if statement inside of the while loop. The ELSE is required, as that is what copies the image as it was originally without any change to that pixel.

    Code (csharp):
    1.  
    2. //INSERT YOUR LOGIC BELOW HERE
    3. if(copiedTexture.GetPixel(x,y) == Color.green) //ex. IF the original pixel is Green
    4. {
    5.        texture.SetPixel(x, y, Color.red);        //Then turn the Green pixel into a Red pixel.
    6. }
    7. //INSERT YOUR LOGIC ABOVE HERE
    8.  
    9. //This line of code is REQUIRED. Do NOT delete it.
    10. //This is what copies the image as it was, without any change to the original pixel.
    11. else
    12. {       texture.SetPixel(x, y, copiedTexture.GetPixel(x,y));
    13. }
    14.  
     
    Last edited: Jul 19, 2020
    Fatamos, Palineto, ledshok and 5 others like this.
  9. outtoplay

    outtoplay

    Joined:
    Apr 29, 2009
    Posts:
    741
    Hey Thanks so much Carter. This is very relevant to me right now. I'm more the artist than the Coder and about to upload a set of Animated 2D Fish Sprites to the asset store (here's the animated demo: https://db.tt/nqNfxYu5). I imagined folks would want to randomly color the fish when instanced for play, so I broke out the Teeth and Eyes as separate sprites on the sprite sheet. This isn't an awful idea as one may want to animate an eye, but assuming not, Just being about to alter the body color would be great. I'm gonna try to implement what you kindly shared. Hopefully I can figure it out.

    Thanks for the great info!
    B.
     
  10. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Sure thing! And rock on! Those fish look great.
     
  11. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Here is an addition which I thought was an easy way to handle changing colors during runtime.

    What this does, is setup a bool to UpdateColors.
    AFTER you change the character's assigned colors in the game, you then either flag it as TRUE in code, or in the editor you can simply click the "Update Colors" box and it will update the sprite. It doesn't update unless this bool is activated, so it never updates the sprite's colors every frame.

    Code (csharp):
    1.  
    2.     public bool UpdateColors = false;
    3.  
    4. public void Update()
    5.     {
    6.  
    7.         if (UpdateColors)
    8.         {
    9.             UpdateCharacterTexture();
    10.             UpdateColors = false;
    11.         }
    12.     }
    13.  


    Here is my entire class, if anyone is interested.
    Like I promised, this includes the loop which will update ALL sprites inside of a spritesheet set to 'Sprite Mode: Multiple'.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System; //added to access enum
    5.  
    6. public class Character : MonoBehaviour
    7. {
    8.     ////Character Data
    9.     //Labels
    10.     public string Name;
    11.     public string Class;
    12.     public string Species;
    13.     public string Gender;
    14.     public bool bearded = false;
    15.     public bool masked = false;
    16.  
    17.     public bool UpdateColors = false;
    18.  
    19.     ///Render Data
    20.     public bool UpdateAnimation = true;
    21.     public string AnimationName;
    22.     public string DirectionName;
    23.     public Texture2D characterTexture2D;
    24.     public Sprite[] characterSprites;
    25.     private string[] names;
    26.     private string spritePath;
    27.  
    28.     public Color Hair = new Color32(96, 60, 40, 255);
    29.     public Color Skin = new Color32(247, 207, 134, 255);
    30.     public Color Pants = new Color32(55, 55, 55, 255);
    31.     public Color ShirtL = new Color32(240, 240, 240, 255);
    32.     public Color ShirtM = new Color32(240, 240, 240, 255);
    33.     public Color ShirtR = new Color32(240, 240, 240, 255);
    34.     public Color Object1 = new Color32(127, 127, 127, 255);
    35.     public Color BeardMask = new Color32(247, 207, 134, 255);
    36.     public Color Badge = new Color32(255, 255, 0, 255);
    37.     public Color Outline = Color.black;
    38.    
    39.     // Use this for initialization
    40.     void Start ()
    41.     {
    42.         TempStart ();
    43.     }
    44.    
    45.     void TempStart()
    46.     {
    47.         Species = "Human";
    48.         Gender = "Male";
    49.         AnimationName = "Idle";
    50.         DirectionName = "S";
    51.         spritePath = ("Characters/" + Species + Gender);
    52.         characterSprites = Resources.LoadAll<Sprite>(spritePath);
    53.         names = new string[characterSprites.Length];
    54.         UpdateCharacterTexture ();
    55.         UpdateAnimationImage();
    56.     }
    57.  
    58.     public Texture2D CopyTexture2D(Texture2D copiedTexture)
    59.     {
    60.         Texture2D texture = new Texture2D(copiedTexture.width, copiedTexture.height);
    61.         texture.filterMode = FilterMode.Point;
    62.         texture.wrapMode = TextureWrapMode.Clamp;
    63.  
    64.         int y = 0;
    65.         while (y < texture.height)
    66.         {
    67.             int x = 0;
    68.             while (x < texture.width)
    69.             {
    70.                 if(copiedTexture.GetPixel(x,y) == new Color32(0,255,0, 255))
    71.                 {
    72.                     if(masked)
    73.                     {
    74.                         texture.SetPixel(x, y, BeardMask);
    75.                     }
    76.                     else if(bearded)
    77.                     {
    78.                         texture.SetPixel(x, y, Hair);
    79.                     }
    80.                     else
    81.                     {
    82.                         texture.SetPixel(x, y, Skin);
    83.                     }
    84.                 }
    85.                 else if(copiedTexture.GetPixel(x,y) == new Color32(255,0,0, 255))
    86.                 {
    87.                     texture.SetPixel(x, y, ShirtR);
    88.                 }
    89.                 else if(copiedTexture.GetPixel(x,y) == new Color32(0,0,255, 255))
    90.                 {
    91.                     texture.SetPixel(x, y, ShirtM);
    92.                 }
    93.                 else if(copiedTexture.GetPixel(x,y) == new Color32(255,255,0 , 255))
    94.                 {
    95.                     texture.SetPixel(x, y, ShirtL);
    96.                 }
    97.                 else if(copiedTexture.GetPixel(x,y) == new Color32(255,0,255, 255 ))
    98.                 {
    99.                     texture.SetPixel(x, y, Badge);
    100.                 }
    101.                 else if(copiedTexture.GetPixel(x,y) == new Color32(55,55,55, 255 ))
    102.                 {
    103.                     texture.SetPixel(x, y, Pants);
    104.                 }
    105.                 else if(copiedTexture.GetPixel(x,y) == new Color32(247,207,134, 255 ))
    106.                 {
    107.                     texture.SetPixel(x, y, Skin);
    108.                 }
    109.                 else if(copiedTexture.GetPixel(x,y) == new Color32(127,127,127, 255 ))
    110.                 {
    111.                     texture.SetPixel(x, y, Object1);
    112.                 }
    113.                 else if(copiedTexture.GetPixel(x,y) == new Color32(96,60,40, 255 ))
    114.                 {
    115.                     texture.SetPixel(x, y, Hair);
    116.                 }
    117.  
    118.                 else if(copiedTexture.GetPixel(x,y) == new Color32(0,0,0, 255 ))
    119.                 {
    120.                     texture.SetPixel(x, y, Outline);
    121.                 }
    122.  
    123.                 //DARK COLORS
    124.                 else if(copiedTexture.GetPixel(x,y) == new Color32(191,0,0, 255))
    125.                 {
    126.                     texture.SetPixel(x, y, DarkenColor(ShirtR, 0.8f));
    127.                 }
    128.                 else if(copiedTexture.GetPixel(x,y) == new Color32(0,0,200, 255))
    129.                 {
    130.                     texture.SetPixel(x, y, DarkenColor(ShirtM, 0.8f));
    131.                 }
    132.                 else if(copiedTexture.GetPixel(x,y) == new Color32(200,200,0 , 255))
    133.                 {
    134.                     texture.SetPixel(x, y, DarkenColor(ShirtL, 0.8f));
    135.                 }
    136.                 else if(copiedTexture.GetPixel(x,y) == new Color32(42,42,42, 255 ))
    137.                 {
    138.                     texture.SetPixel(x, y, DarkenColor(Pants, 0.8f));
    139.                 }
    140.                 else if(copiedTexture.GetPixel(x,y) == new Color32(242,184,77, 255 ) || copiedTexture.GetPixel(x,y) == new Color32(243,188,84, 255) )
    141.                 {
    142.                     texture.SetPixel(x, y, DarkenSkin(Skin, 0.95f));
    143.                 }
    144.                 else if(copiedTexture.GetPixel(x,y) == new Color32(64,64,64, 255 ))
    145.                 {
    146.                     texture.SetPixel(x, y, DarkenColor(Object1, 0.5f));
    147.                 }
    148.                 else if(copiedTexture.GetPixel(x,y) == new Color32(74,48,32, 255) || copiedTexture.GetPixel(x,y) == new Color32(72,45,30, 255) )
    149.                 {
    150.                     texture.SetPixel(x, y, DarkenColor(Hair, 0.8f));
    151.                 }
    152.                
    153.                 else
    154.                 {
    155.                 texture.SetPixel(x, y, copiedTexture.GetPixel(x,y));
    156.                 }
    157.                 ++x;
    158.             }
    159.             ++y;
    160.         }
    161.         texture.name = (Species+Gender);
    162.         texture.Apply();
    163.    
    164.  
    165.         return texture;
    166.     }
    167.  
    168.     public Color32 DarkenColor(Color32 color, float factor)
    169.     {
    170.         Color hexColor = color;
    171.         hexColor.r *= factor;
    172.         hexColor.g *= factor;
    173.         hexColor.b *= factor;
    174.         color = hexColor;
    175.  
    176.         return color;
    177.     }
    178.  
    179.     public Color32 DarkenSkin(Color32 color, float factor)
    180.     {
    181.         //skin      0.969, 0.812, 0.525
    182.         //dark skin 0.949, 0.722, 0.302
    183.         Color hexColor = color;
    184.         hexColor.r -= 0.02f;
    185.         hexColor.g -= 0.09f;
    186.         hexColor.b -= 0.223f;
    187.         hexColor.r *= factor;
    188.         hexColor.g *= factor;
    189.         hexColor.b *= factor;
    190.         color = hexColor;
    191.        
    192.         return color;
    193.     }
    194.  
    195.     public void UpdateCharacterTexture()
    196.     {
    197.         Sprite[] loadSprite = Resources.LoadAll<Sprite> (spritePath);
    198.         characterTexture2D = CopyTexture2D(loadSprite[0].texture);
    199.  
    200.         int i = 0;
    201.         while(i != characterSprites.Length)
    202.         {
    203.             //SpriteRenderer sr = GetComponent<SpriteRenderer>();
    204.             //string tempName = sr.sprite.name;
    205.             //sr.sprite = Sprite.Create (characterTexture2D, sr.sprite.rect, new Vector2(0,1));
    206.             //sr.sprite.name = tempName;
    207.  
    208.             //sr.material.mainTexture = characterTexture2D;
    209.             //sr.material.shader = Shader.Find ("Sprites/Transparent Unlit");
    210.             string tempName = characterSprites[i].name;
    211.             characterSprites[i] = Sprite.Create (characterTexture2D, characterSprites[i].rect, new Vector2(0,1));
    212.             characterSprites[i].name = tempName;
    213.             names[i] = tempName;
    214.             ++i;
    215.         }
    216.  
    217.         SpriteRenderer sr = GetComponent<SpriteRenderer>();
    218.         sr.material.mainTexture = characterTexture2D;
    219.         sr.material.shader = Shader.Find ("Sprites/Transparent Unlit");
    220.  
    221.     }
    222.  
    223.     public void UpdateAnimationImage()
    224.     {
    225.         SpriteRenderer sr = GetComponent<SpriteRenderer> ();
    226.         string animname = DirectionName + "_" + AnimationName;
    227.         sr.sprite = characterSprites [Array.IndexOf (names, animname)];
    228.         UpdateAnimation = false;
    229.     }
    230.    
    231.     public void Update()
    232.     {
    233.         if (UpdateAnimation)
    234.         {
    235.             UpdateAnimationImage();
    236.         }
    237.  
    238.         if (UpdateColors)
    239.         {
    240.             UpdateCharacterTexture();
    241.             UpdateColors = false;
    242.         }
    243.     }
    244.    
    245. }
    246.  
    247.  
    It also includes the logic I used to dye these sprites, into these characters:

    $DyePixels.png

    If you notice, I used all colors that were originally in the sprite, and made them all dyeable.

    This also includes logic to change animations based on direction and animation, such a "S_Idle" or "E_Walk1". As long as the sprite names are correct, such as each sprite being called S_Idle, or South_Idle, etc.
     
    Last edited: Jan 20, 2014
  12. outtoplay

    outtoplay

    Joined:
    Apr 29, 2009
    Posts:
    741
    Wow...this is wicked cool!
    I think this discussion will broaden and more folk will benefit from your example as more devs who are trying to get a handle on 2D workflow search for a solution to color management of sprites. Making instanced sprite more interesting and extend the artwork's useful range is a great option.

    Again thanks for sharing your discoveries and solutions here. It is much appreciated.
     
  13. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    Very cool. I actually went the custom shader route to accomplish this same task, but if I can get this working, I'm sure this will be faster. For posterity, and to add to a great thread, here's my shader code:

    Code (csharp):
    1.  
    2. Shader "Custom/PixelColors" {
    3.     Properties
    4.     {
    5.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    6.         _ColorTint ("Tint", Color) = (1,1,1,1)
    7.         _Color1in ("Color 1 In", Color) = (1,1,1,1)
    8.         _Color1out ("Color 1 Out", Color) = (1,1,1,1)
    9.         _Color2in ("Color 2 In", Color) = (1,1,1,1)
    10.         _Color2out ("Color 2 Out", Color) = (1,1,1,1)
    11.         _Color3in ("Color 3 In", Color) = (1,1,1,1)
    12.         _Color3out ("Color 3 Out", Color) = (1,1,1,1)
    13.         _Color4in ("Color 4 In", Color) = (1,1,1,1)
    14.         _Color4out ("Color 4 Out", Color) = (1,1,1,1)
    15.         [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    16.     }
    17.  
    18.     SubShader
    19.     {
    20.         Tags
    21.         {
    22.             "Queue"="Transparent"
    23.             "IgnoreProjector"="True"
    24.             "RenderType"="Transparent"
    25.             "PreviewType"="Plane"
    26.             "CanUseSpriteAtlas"="True"
    27.         }
    28.  
    29.         Cull Off
    30.         Lighting Off
    31.         ZWrite Off
    32.         Fog { Mode Off }
    33.         Blend SrcAlpha OneMinusSrcAlpha
    34.  
    35.         Pass
    36.         {
    37.         CGPROGRAM
    38.             #pragma vertex vert
    39.             #pragma fragment frag          
    40.             #pragma multi_compile DUMMY PIXELSNAP_ON
    41.             #include "UnityCG.cginc"
    42.            
    43.             struct appdata_t
    44.             {
    45.                 float4 vertex   : POSITION;
    46.                 float4 color    : COLOR;
    47.                 float2 texcoord : TEXCOORD0;
    48.             };
    49.  
    50.             struct v2f
    51.             {
    52.                 float4 vertex   : SV_POSITION;
    53.                 fixed4 color    : COLOR;
    54.                 half2 texcoord  : TEXCOORD0;
    55.             };
    56.            
    57.             fixed4 _ColorTint;
    58.             fixed4 _Color1in;
    59.             fixed4 _Color1out;
    60.             fixed4 _Color2in;
    61.             fixed4 _Color2out;
    62.             fixed4 _Color3in;
    63.             fixed4 _Color3out;
    64.             fixed4 _Color4in;
    65.             fixed4 _Color4out;
    66.  
    67.             v2f vert(appdata_t IN)
    68.             {
    69.                 v2f OUT;
    70.                 OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
    71.                 OUT.texcoord = IN.texcoord;            
    72.                 OUT.color = IN.color * _ColorTint;
    73.                 #ifdef PIXELSNAP_ON
    74.                 OUT.vertex = UnityPixelSnap (OUT.vertex);
    75.                 #endif
    76.  
    77.                 return OUT;
    78.             }
    79.  
    80.             sampler2D _MainTex;        
    81.            
    82.             fixed4 frag(v2f IN) : COLOR
    83.             {
    84.                 float4 texColor = tex2D( _MainTex, IN.texcoord );
    85.                 texColor = all(texColor == _Color1in) ? _Color1out : texColor;
    86.                 texColor = all(texColor == _Color2in) ? _Color2out : texColor;
    87.                 texColor = all(texColor == _Color3in) ? _Color3out : texColor;
    88.                 texColor = all(texColor == _Color4in) ? _Color4out : texColor;
    89.                  
    90.                 return texColor * IN.color;
    91.             }
    92.         ENDCG
    93.         }
    94.     }
    95. }
    96.  
    I started with the basic Default-Sprite shader.
    • Set the sprite sheet's Filter Mode to Point.
    • You may need to override your Format on your target platform to be Truecolor. If the Format is "compressed", then Unity may (and did for me) choose a slightly different pixel color, so your swap out may not work.
    • I don't have any real data to support the speed of this approach to the approach above, but it seems like a custom shader would be slower. I'd love to hear some thoughts here.

    To use, do Assets > Create > Shader, copy/paste the code above, then place the shader file in your Assets folder somewhere. Then, Assets > Create > Material and put the Material in the Resources folder. Assign the new material to use the Shader Custom > PixelColors. In the Material inspector, set "in" colors to be the color to find, and "out" to be the new color. Tint will tint the whole image. Then, after you instantiate your object, use:

    Code (csharp):
    1. TheSpriteGameObject.renderer.material = Resources.Load("YourMaterialName", Material);
    You can also set the colors (for example, to random colors) in script using:

    Code (csharp):
    1.  
    2. TheSpriteGameObject.renderer.material.SetColor("_Color1out", Color(Random.Range(0.0,1.0),Random.Range(0.0,1.0), Random.Range(0.0,1.0)));
    3.  
    Hope this helps someone!
     
    Last edited: Jan 20, 2014
    Fatamos, rujomarco, Voronoi and 4 others like this.
  14. outtoplay

    outtoplay

    Joined:
    Apr 29, 2009
    Posts:
    741
    Very cool... So curious how this works as a shader. Look forward to trying it, and appreciate your sharing as I imagine many here will too.
     
  15. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Shader language looks like gibberish to me, and I didn't want to mess with shaders since I already knew how to do it this way.
    Thank you for this.

    I imagine that if you're changing colors many times at Runtime, a shader would be better on performance by a significant amount.

    But if you're just changing the colors before load, I imagine a shader would be extra draw calls...or the same draw calls?

    I know adding a new texture, means a new draw call. Adding a shader, also means that.

    So in the end, a shader might be better in every way? That is my guess, although I am only relying on a few things I've read from people who know Unity better than I.



    Hopefully I did not miss this, as it's an important step, but I think I did:

    Texture needs to be in Truecolor.

    Even my tiny little pixel guy with only a handful of colors, had Unity's compression totally mess up the RGB(0,255,0) color - which is only 4 pixels, and turned it into 4 different colors. No idea why, as that means MORE colors, not less.



    I also would like to to note that another way to handle it, would be to have text files (similar to ASCII), where each letter is a different color- for pixels.
    I tried this out on my old project, and it worked. What I'd do is make an image in MS Paint, just like I showed you above (my little guy). Then I wrote a program to take that image, and turn it into a text file with a unique letter for each color. In the actual game, I'd read those text files and then create an image from the unique letters- but color the pixel as I saw fit. So the actual game assets were text files, significantly smaller than any .png image file would ever be.

     
    Last edited: Jan 21, 2014
  16. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Compression isn't about fewer colors, it's about representing the colors in a more data-efficient way.

    --Eric
     
    Westland likes this.
  17. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Yea, I guess, but other compression utilities do not destroy my basic sprites like Unity's does. So something is obviously goofy about Unity's compression.
     
  18. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    It's not Unity's compression, it's the graphics card. That's how the hardware works...generally either DXT or PVRTC depending on the GPU, and it's not goofy, it's lossy. Kinda vaguely like JPEG but with a fixed size; since it removes data, what you get out is not what you put in.

    --Eric
     
    Xepherys likes this.
  19. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Well, I thought I knew what I was talking about, seeing as how all other DXT compression utilities are just as lossy.

    $improveit.png

    Since Unity is involved in creating the Texture2D, packing sprites, and all sorts of other stuff- it is certainly Unity, not the graphics card, which results in this problem. Hence Unity's Compression, as opposed to Any-Other-DXT5-Tool's compression.
     
    Last edited: Jan 21, 2014
  20. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    The shader is significantly faster and more efficient.


    When you have more than 1 sprite being loaded, it makes a HUGE difference in performance, as suspected.


    I tried a scene with 11 of my tiny little sprites. Using my method, the load time is SIGNIFICANT. Using your method, the load time is instant.

    That is because in my method, it has to process the copy/write function 11 times. In your function, it's only doing 1 shader.

    Here is a scene with 11 sprites loaded:

    $shader is superior.png

    Also by using your shader, you get to see the correct color change in the Unity Editor, while mine happens at runtime so it is not displayed in the editor.



    Shader is definitely superior in performance.

    However, I am unable to use it because I have 'too many colors', even when I use Shader 3.0

    This is because I do not know enough shader language, to "darken colors" like in my above code.
    Heck, I don't even know how to take the colors out of Unity's Editor GUI but keep them in the shader code so they can't be changed.


    edit: I fixed it by lessening the colors, but this is still a sucky limitation.

    edit2: There is also a big problem when someone changes the pixels to a new color, which matches a different color's in.

    For example, if you have color2IN as pure green (0,1,0), and then change color4OUT to pure green, then it will result in color2OUT and not color4OUT.

    So if my character had a pure green shirt (_Shirt_out) and the _Hair_in was pure green, then the Shader would display the character with a hairy chest :p

    This is a bit of a problem, as one color1_out can force a totally different color2_out to display as color1 and NOT color2.
    I imagine this would be easy to fix, by simply making the shader only check the original texture, not the texture + shader colors (itself).
     
    Last edited: Jan 21, 2014
  21. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    Hmm, I didn't think of what we shall call "The Case of the Green Chest Hair". I'm still a noob in terms of shaders, so I'm not sure how best to handle that. I'd probably just modify the pixel color by a tiny amount so that didn't happen.

    CG coding can be a little intimidating, but it's not too bad if you're just editing. That's the basic sprite-default shader with some mods to the "frag" function. I'd start with this awesome noob guide and just start messing around with it. You're clearly capable based on your previously posted code, it's pretty slick.
     
  22. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Alright, thank you :)

    Yeah, I'd probably patch it with the same method by making it something like "If ColorAout = ColorBin { Color.R - 0.0001 } or something, lol

    Once I figure out the syntax, I'll probably just do that.

    ""The Case of the Green Chest Hair"

    Carter, is on the Case!
    lol
     
  23. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    I'd say this is not really the best way to replace color, because it can have issues with texture compression and interpolation. The code is also not very native to the nature of GPU computations.

    I think a better approach would be to separate the color you want in the red, green and blue channels and supply three replacement colors:
    Code (csharp):
    1.  
    2. texColor = texColor.r * Color1in + texColor.g * Color2in + texColor.b * Color3in;
    3.  
    This approach has no issues with compression or interpolation. And allows you to vary the intensity of each color and even blend them.

    If you have a single replacement color, you can also make that part of the texture white and mark it in the alpha channel to do something like this:
    Code (csharp):
    1.  
    2. texColor.rgb = lerp(texColor.rgb, texColor.rgb * Color1in.rgb, texColor.a);
    3.  
    Finally, if you need to have multiple replacement colors and also some static color information, you can combine two textures:
    Code (csharp):
    1.  
    2. texColor = lerp(texColor, texColor * Color1in, texReplace.r);
    3. texColor = lerp(texColor, texColor * Color2in, texReplace.g);
    4. texColor = lerp(texColor, texColor * Color3in, texReplace.b);
    5.  
     
  24. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I don't understand why this isn't working for me.

    If I use a solid color, like pure green- it changes the color.

    If I used a color like a brown or fleshy tone, it refuses to change the colors. I assume this is because Unity doesn't get the correct RGB value.

    edit: It can't be Unity. It has to be the shader code itself. I'm going to rewrite the entire thing. I should have known better, unless the debugger is a dirty dirty liar it is definitely not Unity.

    edit2: I think I will just give up. Trying to understand this is a bit too much right now. I chose Unity to avoid this type of work...
     
    Last edited: Jan 25, 2014
  25. QPRocky

    QPRocky

    Joined:
    Oct 17, 2012
    Posts:
    56
    Just little tip. Using GetPixels instead GetPixel, you get better performance
     
  26. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Thank you.

    This actually was easy to change, just using two simple Ctrl-H (Replace) clicks.


    Here is the updated script. It has been improved a lot because it is now a script which attaches to a gameobject, and the ONLY function of the script is to dye the pixels.

    IT IS VITAL - TO ENABLE Read/Write on the Texture2D. To enable it on a Sprite, change it from a 'Sprite' to an 'Advanced'. It will still be a Sprite (make sure it is, Advanced - Sprite) but you can now checkmark the box "Read/Write Enabled". This is NOT required with the Shader. It is only required with this class below, which should always be called before a scene fully loads or before the game starts.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class DyePIXELS : MonoBehaviour {
    6.    
    7.         //These are the colors it will change to. The colors that change, are decided in the loop.
    8.     public Color _color1= new Color32(96, 60, 40, 255);
    9.     public Color _color2= new Color32(247, 207, 134, 255);
    10.     public Color _color3= new Color32(55, 55, 55, 255);
    11.     public Color _color4= new Color32(240, 240, 240, 255);
    12.     public Color _color5= new Color32(240, 240, 240, 255);
    13.     public Color _color6= new Color32(240, 240, 240, 255);
    14.     public Color _color7= new Color32(127, 127, 127, 255);
    15.     public Color _color8= new Color32(247, 207, 134, 255);
    16.     public Color _color9= new Color32(255, 255, 0, 255);
    17.     public Color _color0= Color.black;
    18.    
    19.         //This is required if there is any data in another script. I store all the character's data inside of the characterScript.
    20.     private Character characterScript;
    21.    
    22.     private string spritePath;
    23.     public Texture2D characterTexture2D;
    24.    
    25.    
    26.     void Start ()
    27.     {
    28.                //My characterScript is called Character.cs
    29.         characterScript = GetComponent<Character>();
    30.  
    31.                 //This is the path to the correct Advanced Read/Write enabled spritesheet.
    32.         spritePath = ("Characters/" + characterScript.Species + characterScript.Gender);
    33.  
    34.                //This is my array of sprites
    35.         characterScript.characterSprites = Resources.LoadAll<Sprite>(spritePath);
    36.                //And the names of the sprites.
    37.         characterScript.names = new string[characterScript.characterSprites.Length];
    38.        
    39.                 //I call this once, so that the character doesn't start the game undyed.
    40.         UpdateCharacterTexture ();
    41.     }
    42.    
    43.    
    44.     public void Update()
    45.     {
    46.                //This bool flag prevents Update from calling the function every frame. Instead, it only updates the texture2D whenever the         user tells it to.
    47.         if (characterScript.UpdateColors)
    48.         {
    49.             UpdateCharacterTexture();
    50.             characterScript.UpdateColors = false;
    51.         }
    52.     }
    53.    
    54.    
    55.     public Texture2D CopyTexture2D(Texture2D copiedTexture)
    56.     {
    57.         Texture2D texture = new Texture2D(copiedTexture.width, copiedTexture.height);
    58.         texture.filterMode = FilterMode.Point;
    59.         texture.wrapMode = TextureWrapMode.Clamp;
    60.        
    61.         Color[] pixelColors = copiedTexture.GetPixels (0, 0, copiedTexture.width, copiedTexture.height);
    62.  
    63.         int y = 0;
    64.         while (y < pixelColors.Length)
    65.         {
    66.                 if(pixelColors[y] == new Color32(0,255,0, 255))
    67.                 {
    68.                     if(masked)
    69.                     {
    70.                         pixelColors[y] = (_color1);
    71.                     }
    72.                     else if(bearded)
    73.                     {
    74.                         pixelColors[y] = (_color2);
    75.                     }
    76.                     else
    77.                     {
    78.                         pixelColors[y] = (_color3);
    79.                     }
    80.                 }
    81.                 else if(pixelColors[y] == new Color32(255,0,0, 255))
    82.                 {
    83.                     pixelColors[y] = (_color4);
    84.                 }
    85.                 else if(pixelColors[y] == new Color32(0,0,255, 255))
    86.                 {
    87.                     pixelColors[y] = (_color5);
    88.                 }
    89.                 else if(pixelColors[y] == new Color32(255,255,0 , 255))
    90.                 {
    91.                     pixelColors[y] = (_color6);
    92.                 }
    93.                 else if(pixelColors[y] == new Color32(255,0,255, 255 ))
    94.                 {
    95.                     pixelColors[y] = (_color7);
    96.                 }
    97.                 else if(pixelColors[y] == new Color32(55,55,55, 255 ))
    98.                 {
    99.                     pixelColors[y] = (_color8);
    100.                 }
    101.                 else if(pixelColors[y] == new Color32(247,207,134, 255 ))
    102.                 {
    103.                     pixelColors[y] = (_color3);
    104.                 }
    105.                 else if(pixelColors[y] == new Color32(127,127,127, 255 ))
    106.                 {
    107.                     pixelColors[y] = (_color9);
    108.                 }
    109.                 else if(pixelColors[y] == new Color32(96,60,40, 255 ))
    110.                 {
    111.                     pixelColors[y] = (_color2);
    112.                 }
    113.                
    114.                 else if(pixelColors[y] == new Color32(0,0,0, 255 ))
    115.                 {
    116.                     pixelColors[y] = (_color0);
    117.                 }
    118.                
    119.                 //DARK COLORS
    120.                 else if(pixelColors[y] == new Color32(191,0,0, 255))
    121.                 {
    122.                     pixelColors[y] = (DarkenColor(_color2, 0.8f));
    123.                 }
    124.                 else if(pixelColors[y] == new Color32(0,0,200, 255))
    125.                 {
    126.                     pixelColors[y] = (DarkenColor(_color3, 0.8f));
    127.                 }
    128.                 else if(pixelColors[y] == new Color32(200,200,0 , 255))
    129.                 {
    130.                     pixelColors[y] = (DarkenColor(_color4, 0.8f));
    131.                 }
    132.                 else if(pixelColors[y] == new Color32(42,42,42, 255 ))
    133.                 {
    134.                     pixelColors[y] = (DarkenColor(_color5, 0.8f));
    135.                 }
    136.                 else if(pixelColors[y] == new Color32(242,184,77, 255 ) || pixelColors[y] == new Color32(243,188,84, 255) )
    137.                 {
    138.                     pixelColors[y] = (DarkenSkin(_color3, 0.95f));
    139.                 }
    140.                 else if(pixelColors[y] == new Color32(64,64,64, 255 ))
    141.                 {
    142.                     pixelColors[y] = (DarkenColor(_color6, 0.5f));
    143.                 }
    144.                 else if(pixelColors[y] == new Color32(74,48,32, 255) || pixelColors[y] == new Color32(72,45,30, 255) )
    145.                 {
    146.                     pixelColors[y] = (DarkenColor(_color2, 0.8f));
    147.                 }
    148.                
    149.                 else
    150.                 {
    151.                     pixelColors[y] = (pixelColors[y]);
    152.                 }
    153.  
    154.             ++y;
    155.         }
    156.  
    157.         texture.SetPixels (pixelColors);
    158.  
    159.                //My original spritesheet had this name, so I simply give it the name again. In my game, this is important.
    160.         texture.name = (characterScript.Species+characterScript.Gender);
    161.         texture.Apply();
    162.        
    163.        
    164.         return texture;
    165.     }
    166.    
    167.  
    168.         //This function helps me to have a pixel shading effect by making some shadows or outlines darker. The function takes a Color32 and turns it into a decimal Color, darkens it, and then returns it as a Color32.
    169.     public Color32 DarkenColor(Color32 color, float factor)
    170.     {
    171.         Color hexColor = color;
    172.         hexColor.r *= factor;
    173.         hexColor.g *= factor;
    174.         hexColor.b *= factor;
    175.         color = hexColor;
    176.        
    177.         return color;
    178.     }
    179.    
    180.        //This is the same darken color function, but it changes the color a bit more for my skin tones to look better
    181.     public Color32 DarkenSkin(Color32 color, float factor)
    182.     {
    183.         //skin      0.969, 0.812, 0.525
    184.         //dark skin 0.949, 0.722, 0.302
    185.         Color hexColor = color;
    186.         hexColor.r -= 0.02f;
    187.         hexColor.g -= 0.09f;
    188.         hexColor.b -= 0.223f;
    189.         hexColor.r *= factor;
    190.         hexColor.g *= factor;
    191.         hexColor.b *= factor;
    192.         color = hexColor;
    193.        
    194.         return color;
    195.     }
    196.    
    197.         //This assigns the new, altered Texture2D to the sprite. It also assigns my material.
    198.     public void UpdateCharacterTexture()
    199.     {
    200.         Sprite[] loadSprite = Resources.LoadAll<Sprite> (spritePath);
    201.         characterTexture2D = CopyTexture2D(loadSprite[0].texture);
    202.        
    203.         int i = 0;
    204.         while(i != characterScript.characterSprites.Length)
    205.         {
    206.             string tempName = characterScript.characterSprites[i].name;
    207.             characterScript.characterSprites[i] = Sprite.Create (characterTexture2D, characterScript.characterSprites[i].rect, new Vector2(0,1));
    208.             characterScript.characterSprites[i].name = tempName;
    209.             characterScript.names[i] = tempName;
    210.             ++i;
    211.         }
    212.        
    213.         SpriteRenderer sr = GetComponent<SpriteRenderer>();
    214.         sr.material.mainTexture = characterTexture2D;
    215.         sr.material.shader = Shader.Find ("Sprites/Transparent Unlit");
    216.         sr.material.color = new Color (1, 1, 1, 1);
    217.        
    218.     }
    219. }

    I hope this helps anyone, as this not only has the function to change the color of specific pixels to any color you want, but it also holds the logic to be a separate script which accesses another script (DyePIXELS.cs accesses variables from Character.cs), holds the functions to change colors by darkening them or adjusting the RGB values, and very importantly IMO helps to show how to use an intuitive and natural RGB Color32 instead of Unity's horrific decimal Color.






    The performance increase is as follows, for my test scene:


    1 Sprite on "Play"
    1.77s (GetPixels / SetPixels method)
    2.34s (GetPixel, SetPixel former method)


    10 Sprites on "Play"
    4.68s (GetPixels / SetPixels)
    7.78s (GetPixel / SetPixel)





    The difference is quite minor, but it does almost half the load time when 10 sprites are being dyed on load.
    It's also quite minor because my sprites are 40x40 little pixel guys. If they were bigger, I'm sure it would be an enormous difference.
     
    Last edited: Feb 9, 2014
    Palineto and esco1979 like this.
  27. AtrujanoX

    AtrujanoX

    Joined:
    Jul 24, 2012
    Posts:
    2
    Carter I need help.
    I got your awesome script and almost everything works fine but the UpdateCharacterTexture() function isn't changing my sprites of the character, it just changes the texture of the new sprites but doesn't apply it to the renderer, I have an animation dragged from selecting multiple sprites of a single spritesheet
    The original player color is red

    $Sin título.jpg

    Can you help me? :( I modified your code a bit. Thanks
    p.d. Sorry for English if bad

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Cambiacolores : MonoBehaviour
    5. {
    6.     public Texture2D texturaReferencia;
    7.     public Sprite[] characterSprites;
    8.     private string spritePath;
    9.     public Texture2D characterTexture2D;
    10.     private string[] names;
    11.     public bool UpdateColors = true;
    12.     public int pixelTo;
    13.    
    14.     void Start (){
    15.         //This is the path to the correct Advanced Read/Write enabled spritesheet.
    16.         spritePath = ("Sprites/Character");
    17.         //This is my array of sprites
    18.         characterSprites = Resources.LoadAll<Sprite> (spritePath);
    19.         //And the names of the sprites.
    20.         names = new string[characterSprites.Length];
    21.         //I call this once, so that the character doesn't start the game undyed.
    22.         UpdateCharacterTexture ();             
    23.     }
    24.    
    25.     public void Update ()
    26.     {
    27.         //This bool flag prevents Update from calling the function every frame. Instead, it only updates the texture2D whenever the         user tells it to.
    28.         if (UpdateColors) {
    29.             UpdateCharacterTexture ();
    30.             UpdateColors = false;
    31.         }
    32.        
    33.     }
    34.    
    35.     public Texture2D CopyTexture2D (Texture2D copiedTexture)
    36.     {
    37.         Texture2D texture = new Texture2D (copiedTexture.width, copiedTexture.height);
    38.         texture.filterMode = FilterMode.Point;
    39.         texture.wrapMode = TextureWrapMode.Clamp;
    40.         Color[] pixelColors = copiedTexture.GetPixels (0, 0, copiedTexture.width, copiedTexture.height);
    41.         int y = 0;
    42.         while (y < pixelColors.Length) {
    43.             for (int i = 0; i<5;i++){
    44.                 if (pixelColors [y] == texturaReferencia.GetPixel (i, 0)){
    45.                     pixelColors [y] = texturaReferencia.GetPixel (i, pixelTo);
    46.                 }
    47.             }
    48.             y++;
    49.         }
    50.         texture.SetPixels (pixelColors);
    51.         //My original spritesheet had this name, so I simply give it the name again. In my game, this is important.
    52.         texture.name = ("player");
    53.         texture.Apply ();
    54.         return texture;
    55.     }
    56.    
    57.    
    58.    
    59.     //This assigns the new, altered Texture2D to the sprite. It also assigns my material.
    60.     public void UpdateCharacterTexture ()
    61.     {
    62.         Sprite[] loadSprite = Resources.LoadAll<Sprite> (spritePath);
    63.         characterTexture2D = CopyTexture2D (loadSprite[0].texture);
    64.         int i = 0;
    65.         while (i != characterSprites.Length) {
    66.             string tempName = characterSprites [i].name;
    67.             characterSprites[i] = Sprite.Create(characterTexture2D, characterSprites[i].rect, new Vector2 (0.5f, 5f), 20f);
    68.             characterSprites[i].name = tempName;
    69.             names[i] = tempName;
    70.             i++;
    71.         }
    72.        
    73.         SpriteRenderer sr = GetComponent<SpriteRenderer> ();
    74.         sr.material.mainTexture = characterTexture2D;
    75.         sr.material.shader = Shader.Find ("Sprites/Default");
    76.         sr.material.color = Color.white;
    77.     }
    78.    
    79. }
     
    Last edited: Feb 9, 2014
  28. AtrujanoX

    AtrujanoX

    Joined:
    Jul 24, 2012
    Posts:
    2
    Sorry. Solved it! I just added a "target" texture and made my sprites there Here's the new code:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Cambiacolores : MonoBehaviour
    5. {
    6.     public Texture2D texturaReferencia;
    7.     public Texture2D sourceTexture;
    8.     public Texture2D targetTexture;
    9.     public Sprite[] characterSprites;
    10.     private string spritePath;
    11.     public Texture2D characterTexture2D;
    12.     private string[] names;
    13.     public bool UpdateColors = true;
    14.     public int pixelTo;
    15.    
    16.     void Start (){
    17.         //My characterScript is called Character.cs
    18.         //This is the path to the correct Advanced Read/Write enabled spritesheet.
    19.         spritePath = ("Sprites/Character");
    20.         //This is my array of sprites
    21.         characterSprites = Resources.LoadAll<Sprite> (spritePath);
    22.         //And the names of the sprites.
    23.         names = new string[characterSprites.Length];
    24.         //I call this once, so that the character doesn't start the game undyed.
    25.         UpdateCharacterTexture ();             
    26.     }
    27.    
    28.     public void Update ()
    29.     {
    30.         //This bool flag prevents Update from calling the function every frame. Instead, it only updates the texture2D whenever the         user tells it to.
    31.         if (UpdateColors) {
    32.             UpdateCharacterTexture ();
    33.             UpdateColors = false;
    34.         }
    35.        
    36.     }
    37.    
    38.     public Texture2D CopyTexture2D (Texture2D copiedTexture)
    39.     {
    40.         Texture2D texture = new Texture2D (copiedTexture.width, copiedTexture.height);
    41.         texture.filterMode = FilterMode.Point;
    42.         texture.wrapMode = TextureWrapMode.Clamp;
    43.         Color[] pixelColors = copiedTexture.GetPixels (0, 0, copiedTexture.width, copiedTexture.height);
    44.         Color[] targetColors = targetTexture.GetPixels (0, 0, copiedTexture.width, copiedTexture.height);
    45.         int y = 0;
    46.         while (y < pixelColors.Length) {
    47.             for (int i = 0; i<5;i++){
    48.                 if (pixelColors [y] == texturaReferencia.GetPixel (i, 0)){
    49.                     pixelColors [y] = texturaReferencia.GetPixel (i, pixelTo);
    50.                     targetColors [y] = texturaReferencia.GetPixel (i, pixelTo);
    51.                 }
    52.             }
    53.             y++;
    54.         }
    55.         texture.SetPixels (pixelColors);
    56.         targetTexture.SetPixels (targetColors);
    57.         //My original spritesheet had this name, so I simply give it the name again. In my game, this is important.
    58.         texture.name = ("player");
    59.         texture.Apply ();
    60.         targetTexture.Apply();
    61.         return texture;
    62.     }
    63.    
    64.    
    65.    
    66.     //This assigns the new, altered Texture2D to the sprite. It also assigns my material.
    67.     public void UpdateCharacterTexture ()
    68.     {
    69.         Sprite[] loadSprite = Resources.LoadAll<Sprite> (spritePath);
    70.         //      characterTexture2D = CopyTexture2D (loadSprite[0].texture);
    71.         characterTexture2D = CopyTexture2D (sourceTexture);
    72.         int i = 0;
    73.         while (i != characterSprites.Length) {
    74.             string tempName = characterSprites [i].name;
    75.             characterSprites[i] = Sprite.Create(characterTexture2D, characterSprites[i].rect, new Vector2 (0.5f, 5f), 20f);
    76.             characterSprites[i].name = tempName;
    77.             names[i] = tempName;
    78.             i++;
    79.         }
    80.        
    81.         SpriteRenderer sr = GetComponent<SpriteRenderer> ();
    82.         sr.material.mainTexture = characterTexture2D;
    83.         sr.material.shader = Shader.Find ("Sprites/Default");
    84.         sr.material.color = Color.white;
    85.     }
    86.    
    87. }
     
  29. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I am not sure if I already said this or not, but it is faster just typing it out again instead of re-reading this thread.

    Here is another way to change the color of certain pixels in a single sprite: Use a prehistoric, weird method to render. I created this method to fix a bug in Unity which should be fixed in 4.5 update. The text file is actually smaller than the png file, if you do not include the second color text file.



    What I do is I draw an image in PAINT.NET, and then I use a Unity project to take the image and convert it into two files. The first, is a text asset with integers for each individual color. So if you have 8 colors in your pixel art, you will have integers from 0 to 7. I always make 0 the alpha, but you have to be careful with that.

    Code (csharp):
    1.  
    2. 0000000000000
    3. 0000122130000
    4. 0001414243000
    5. 0014545354100
    6. 0131245434310
    7. 0341434545430
    8. 0234345434530
    9. 0122454434320
    10. 0343454342210
    11. 0034543554300
    12. 0003422424000
    13. 0000324130000
    14. 0000000000000
    15.  
    The second text file holds the colors, in the form of

    Code (csharp):
    1.  
    2. Color0 = new Color32(255,255,255,255);
    3. Color1 = new Color32(199,43,18,255);
    4. Color2 = new Color32(165,29,15,255);
    5. Color3 = new Color32(231,85,12,255);
    6. Color4 = new Color32(254,170,14,255);
    7. Color5 = new Color32(255,228,110,255);
    8. Color6 = new Color32(0,0,0,255);
    9. Color7 = new Color32(0,0,0,255);
    10. Color8 = new Color32(0,0,0,255);
    11. Color9 = new Color32(0,0,0,255);
    12.  

    In my project, I load the TextAsset, and render it pixel by pixel (using Vectrosity, but you could use any method).

    HotPlanet.png


    If I want to change the color, it is literally as simple as Color0 = Color.blue; or Color1 = Color.red;


    This of course, only works when instantiating it. You'd have to destroy and recreate the object to change the colors during runtime. I just thought it was interesting enough to share.



    Here is the project (just one class) which takes a texture and outputs the two text files.
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.IO;
    5.  
    6. public class ImageToText : MonoBehaviour
    7. {
    8.    public Texture2D texture;
    9.    bool newColor = true;
    10.  
    11.    void Start()
    12.    {
    13.      string filename = texture.name;
    14.  
    15.      int z = 0;
    16.      Color32[] UniqueColors = new Color32[10];
    17.  
    18.      //Find all Unique Colors, and put them in the Color32[] Array.
    19.      for (int y = 0; y < texture.height; y++)
    20.      {
    21.        for (int x = 0; x < texture.width; x++)
    22.        {
    23.          Color32 pixelColor = texture.GetPixel(x, y);
    24.  
    25.            bool sameColor = false;
    26.  
    27.            for(int i = z; i >= 0; i--)
    28.            {
    29.              if(pixelColor.r == UniqueColors[i].r && pixelColor.g == UniqueColors[i].g && pixelColor.b == UniqueColors[i].b)
    30.              {
    31.                sameColor = true;
    32.              }
    33.            }
    34.  
    35.            if(!sameColor)
    36.            {
    37.              UniqueColors[z] = pixelColor;
    38.              z++;
    39.            }
    40.  
    41.            
    42.  
    43.  
    44.        }
    45.      }
    46.  
    47.  
    48.    
    49.      string[] lines;
    50.      lines = new string[100]; //MAX OF Size Y = 100 (100x100, 999x100, 1x100)
    51.      string line = "";
    52.  
    53.  
    54.      for (int y = 0; y < texture.height; y++)
    55.      {
    56.        for (int x = 0; x < texture.width; x++)
    57.        {
    58.          Color32 pixelColor = texture.GetPixel(x, y);
    59.          int colorNum = 0;
    60.  
    61.  
    62.          for(int i = 0; i < 10; i++)
    63.          {
    64.          
    65.            if(pixelColor.r == UniqueColors[i].r && pixelColor.g == UniqueColors[i].g && pixelColor.b == UniqueColors[i].b)
    66.            {
    67.              colorNum = i;
    68.              break;
    69.            }
    70.          }
    71.  
    72.            line = line + colorNum;
    73.  
    74.        }
    75.        //New Line
    76.        lines[y] = line;
    77.        line = null;
    78.      }
    79.       System.IO.File.WriteAllLines(("Assets//Textures//Created//" + filename + ".txt"), lines);
    80.  
    81.   string[] colorsLines;
    82.   colorsLines = new string[100];
    83.  
    84.   for (int u = 0; u < UniqueColors.Length; u++ )
    85.   {
    86.   string colorline = "Color" + u + " = new Color32(" + UniqueColors[u].r + "," + UniqueColors[u].g + "," + UniqueColors[u].b + "," + 255 + ");";
    87.  
    88.   colorsLines[u] = colorline;
    89.   }
    90.  
    91.  
    92.  
    93.  
    94.   System.IO.File.WriteAllLines(("Assets//Textures//Created//" + filename + "_Color.txt"), colorsLines);
    95.    }
    96.  
    97. }
    98.  

    If you want to use it, just add an empty game object into a scene, attach ImageToText class to it, and then attach the texture you want to convert into the public texture slot. Hit "Play", and it's done.
     
    Last edited: Jun 14, 2014
  30. f_rodrigues

    f_rodrigues

    Joined:
    Dec 1, 2013
    Posts:
    1
    I'm trying to increment your Shader to support 22 colors, I was able to get up to 15 but beyond that I get:


    Code (CSharp):
    1. Shader error in 'Custom/PixelColors': Program 'frag', Constant register limit exceeded; more than 32 constant registers needed to compiled program at line 73
    Is there a way to increase this limit?
     
  31. cf

    cf

    Joined:
    Jun 17, 2014
    Posts:
    2
    If the original sprites were part of a Unity Animation used in a Unity Animator Controller, does anyone know how to use the modified sprites in the original Animation/Animator Controller WITHOUT using Update/LateUpdate/FixedUpdate in a script?

    Can the original Animation/Animator Controller be edited at runtime? Or can they be copied and the copy be edited at runtime?
     
  32. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    You should probably ask this question in a new thread. That would both get more attention on it, and keep it to be more on topic.
     
  33. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I don't believe so, but also yes. From what I remember, this all depends on the shader level you choose for your project. I think you can change it to increase it, but I'm not sure if it increases the limit beyond 32 or not.

    http://docs.unity3d.com/Manual/SL-ShaderPrograms.html
     
  34. TippyK

    TippyK

    Joined:
    Dec 3, 2013
    Posts:
    3
    Reviving this old thread..

    The shader version works great for changing colors, but I cant seem to get the shader to work with lights. The sprite stays fully lit instead of reacting to light. The shader is pretty much identical to the built in sprite/diffuse shader which responds correctly to light, so what could be the issue?? Any help on this would be great.
     
  35. TippyK

    TippyK

    Joined:
    Dec 3, 2013
    Posts:
    3
    I have also switched the Lighting component to On within the shader script.
     
  36. DreamingSpirit

    DreamingSpirit

    Joined:
    Apr 29, 2013
    Posts:
    8
    I am about to make my own shader implementation for solving this problem and hit this thread. I would like to share some ideas to circumvent the color limit and how to extend this approach further to also support normals, specular, etc..

    Color limit and color palletes
    The way I see it the challenge is how to pass instance specific data to the shader beyond the instance specific vertex color that is provided in the sprite renderer component. I think the solution could be to use the instance specific vertex color not as a color but as an index/reference where the material (shared data) has a color palette(set of colors) matching to that index/reference.

    This would mean replacing this line of code in the default sprite shader:
    Code (Shader Code):
    1. o.color = v.color * _Color;
    And using the v.color's color channels to form some sort of id or "instance_index". Each 8bit color channel, like the swizzled v.color.r, could store 256 different values and by combining the values of the channels you can go up to 256 to the power of 4 (32bit) possibilities for different set lookups.

    The color sets themselves could be stored in the material as a texture. Where for example each line in the texture describes one color set. This texture could be pre-generated/pre-baked to avoid any hiccups during the starting phase of the game. Retrieving the set's starting point in the shader could be done by using the previously described index.

    The actual color on a certain pixel in the pixel shader can be retrieved by doing a:
    Code (Shader Code):
    1. tex2D(my_lookup_texture, UV(instance_index, sprite_index))
    Where the sprite_index is using a similar indexing approach on all the colors inside your actual sprites(textures). For example the red channel of your sprite could represent the index for which color to take from the color palette.

    This will require your sprites to be prepared or processed in a certain way. Where you could ask the artist to use for example the red channel to define the color tint of the sprite (for example a white for color tint 1 (for clothing tint 1), a light grey for color tint 2 (for the hair), etc.).

    This way the sprites can still be animated and batched as usual, by using unity's sprite packer for example to further reduce drawcalls through batching.

    Extending this idea to support normals, prebaked lighting, specular, alpha, etc.
    This idea could be further extended by using the using the color of the sprite as an index to hold indices to look up color ramp values for the normals, specular, alpha, prebaked lighting and any other information you wish to be defined through the sprite. The simplest approach would be to use each channel of the channel to minimalistically describe certain information (red for color tint, the green channel could be the baked in shading/ambient occlusion, etc.).

    The problem with this one channel (or more channels) per sprite information type approach is that you will run out of color space in the original sprite if you try to pack them together this way.

    TLDR
    Use the instance based vertex color value of the spriterenderer and the shared color values in the sprite to define what colors from the color sets to use in the shader.

    If anyone is interested I could make an asset store package to efficiently solve this problem by assembling multiple sprites into one (from multiple seperate textures), handling and editor inspecting the creation of color palettes and a bunch of sprite shaders implementations (unlit, diffuse, normal mapped) to go along with it.
     
  37. ben.aten

    ben.aten

    Joined:
    Jan 10, 2013
    Posts:
    8
  38. truecrisis

    truecrisis

    Joined:
    Feb 17, 2015
    Posts:
    8
    OMG after all afternoon I finally got the shader to work. There were some bugs, as found earlier, if you didn't use 100% shades of a color, you couldn't replace your sample color with desired color. Using a threshold, its possible to account for whatever problem is causing values with the eyedropper to not be correct.

    I've improved the code to also account for ALPHA values too. This does not respect lighting like the past few comments requested (I have no need to implement lighting, and I'm a noob).

    If you don't want to use my code but want values to work that isn't like 100% green, just take a look at the section where I commented about "threshold"

    Code (CSharp):
    1. Shader "Custom/SetAlpha" {
    2.     Properties
    3.     {
    4.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    5.         _ColorTint ("Tint", Color) = (1,1,1,1)
    6.         _Colorin ("Color In", Color) = (1,1,1,1)
    7.         _Colorout ("Color Out", Color) = (1,1,1,1)
    8.         _ReplaceColorCutoff("Color Replace Threshold", float) = 0
    9.         [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    10.         _AlphaColor("AlphaColor", Color) = (0,0,0,1)
    11.         _AlphaColorCutoff("Alpha Threshold", float) = 0
    12.     }
    13.     SubShader
    14.     {
    15.         Tags
    16.         {
    17.             "Queue"="Transparent"
    18.             "IgnoreProjector"="True"
    19.             "RenderType"="Transparent"
    20.             "PreviewType"="Plane"
    21.             "CanUseSpriteAtlas"="True"
    22.         }
    23.         Cull Off
    24.         Lighting Off
    25.         ZWrite Off
    26.         Fog { Mode Off }
    27.         Blend SrcAlpha OneMinusSrcAlpha
    28.         Pass
    29.         {
    30.         CGPROGRAM
    31.             #pragma vertex vert
    32.             #pragma fragment frag        
    33.             #pragma multi_compile DUMMY PIXELSNAP_ON
    34.             #include "UnityCG.cginc"
    35.  
    36.          sampler2D _MainTex;
    37.          fixed4 _AlphaColor;
    38.          
    39.          
    40.             struct appdata_t
    41.             {
    42.                 float4 vertex   : POSITION;
    43.                 float4 color    : COLOR;
    44.                 float2 texcoord : TEXCOORD0;
    45.             };
    46.             struct v2f
    47.             {
    48.                 float4 vertex   : SV_POSITION;
    49.                 fixed4 color    : COLOR;
    50.                 half2 texcoord  : TEXCOORD0;
    51.             };
    52.          
    53.             fixed4 _ColorTint;
    54.             fixed4 _Colorin;
    55.             fixed4 _Colorout;
    56.             float _AlphaColorCutoff;
    57.             float _ReplaceColorCutoff;
    58.             v2f vert(appdata_t IN)
    59.             {
    60.                 v2f OUT;
    61.                 OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
    62.                 OUT.texcoord = IN.texcoord;      
    63.                 //This didn't work, so commenting it out, added it in fragment shader
    64.                // OUT.color = IN.color * _ColorTint;
    65.                 #ifdef PIXELSNAP_ON
    66.                 OUT.vertex = UnityPixelSnap (OUT.vertex);
    67.                 #endif
    68.                
    69.                 return OUT;
    70.             }
    71.        
    72.          
    73.             fixed4 frag(v2f IN) : COLOR
    74.             {
    75.                 float4 texColor = tex2D( _MainTex, IN.texcoord );
    76.                
    77.                
    78.                 if( //the threshold is needed because exact values cant be used
    79.                     (abs(texColor.r - _Colorin.r) < _ReplaceColorCutoff) &&
    80.                     (abs(texColor.g - _Colorin.g) < _ReplaceColorCutoff) &&
    81.                     (abs(texColor.b - _Colorin.b) < _ReplaceColorCutoff))
    82.                 { //replace RGB and alpha with colorout RGB and alpha
    83.                     texColor = _Colorout;
    84.                  }
    85.  
    86.                 if(
    87.                     (abs(texColor.r - _AlphaColor.r) < _AlphaColorCutoff) &&
    88.                     (abs(texColor.g - _AlphaColor.g) < _AlphaColorCutoff) &&
    89.                     (abs(texColor.b - _AlphaColor.b) < _AlphaColorCutoff))
    90.                 { //replace replace alpha if matches this color
    91.                     texColor.a = 0;
    92.                 }
    93.  
    94.                 return texColor * _ColorTint; //apply the tint
    95.             }
    96.         ENDCG
    97.         }
    98.     }
    99. }
     
    henners999 likes this.
  39. breakfeast

    breakfeast

    Joined:
    Nov 20, 2014
    Posts:
    1
    Hi folks,

    I cobbled together a palette-swapping shader last night that, having breezed through this thread, seems to work pretty much exactly the way the ones posted here do.

    However I've noticed a big issue with it: my game's camera zooms in and out a lot during gameplay, and when it zooms out, the edges of the colours it reads at the 'frag' stage get blurred together, and so it leaves crappy unrecoloured edges where the colours are getting combined. (I have a threshold like the poster above, which gives it some wiggle room -- but to fix this it needs to be set ever higher and higher the further zoomed out I get; def doesn't seem like the answer.)


    I'm completely new to shaders so my troubleshooting powers are extremely limited... It seems like what should properly fix this would be to replace colours in the image information before plugging it in and displaying it, but my initial attempts at manipulating the colours in the (appdata_t IN) that's passed into vert have been 0% successful, either yielding all-black sprites or just breaking it. Could anyone shed some light on how best to go about this in the shader? (or am I better off going back to just creating & using palette-swapped textures during gameplay...?)
     
  40. AminMoazzen

    AminMoazzen

    Joined:
    Aug 8, 2012
    Posts:
    1
    Hi,
    I had similar problem but I needed a shader to replace colors with gradient.
    I played around with shader codes posted here and wrote a shader that i think have smoother result and better code but unfortunately u can only choose one color to replace and should replace that and rest of the texture color.
    I'm no shader expert so it'll be nice if someone improve the code and functionality ;)
    Here is the code and the result:
    shader result.png
    Code (CSharp):
    1. Shader "Custom/ReplaceColor" {
    2.     Properties
    3.     {
    4.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    5.         _ColorIn ("Color to replace", Color) = (1,1,1,1)
    6.         _Color1out ("Replace color", Color) = (1,1,1,1)
    7.         _Color2out ("Color for other areas", Color) = (1,1,1,1)
    8.     }
    9.     SubShader
    10.     {
    11.         Tags
    12.         {
    13.             "Queue"="Transparent"
    14.             "IgnoreProjector"="True"
    15.             "RenderType"="Transparent"
    16.             "PreviewType"="Plane"
    17.             "CanUseSpriteAtlas"="True"
    18.         }
    19.         Cull Off
    20.         Lighting Off
    21.         ZWrite Off
    22.         Fog { Mode Off }
    23.         Blend SrcAlpha OneMinusSrcAlpha
    24.         Pass
    25.         {
    26.         CGPROGRAM
    27.             #pragma vertex vert
    28.             #pragma fragment frag      
    29.             #pragma multi_compile DUMMY PIXELSNAP_ON
    30.             #include "UnityCG.cginc"
    31.        
    32.             struct appdata_t
    33.             {
    34.                 float4 vertex   : POSITION;
    35.                 fixed4 color    : COLOR;
    36.                 half2 texcoord : TEXCOORD0;
    37.             };
    38.             struct v2f
    39.             {
    40.                 float4 vertex   : SV_POSITION;
    41.                 fixed4 color    : COLOR;
    42.                 half2 texcoord  : TEXCOORD0;
    43.             };
    44.        
    45.             fixed4 _ColorIn;
    46.             fixed4 _Color1out;
    47.             fixed4 _Color2out;
    48.             v2f vert(appdata_t IN)
    49.             {
    50.                 v2f OUT;
    51.                 OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
    52.                 OUT.texcoord = IN.texcoord;        
    53.                 OUT.color = IN.color;
    54.                 #ifdef PIXELSNAP_ON
    55.                     OUT.vertex = UnityPixelSnap (OUT.vertex);
    56.                 #endif
    57.                 return OUT;
    58.             }
    59.             sampler2D _MainTex;    
    60.        
    61.             fixed4 frag(v2f IN) : COLOR
    62.             {
    63.                 fixed4 texColor = tex2D( _MainTex, IN.texcoord );
    64.  
    65.                 //Distance is used to know the difference
    66.                 // between texture color and the chosen color for replacement
    67.                 fixed dist1 = distance(texColor, _ColorIn);
    68.  
    69.                 //We want to have the first color when the texture color is nearly
    70.                 // similar to the chosen color and slowly switch to second color
    71.                 // as the color gets more different.
    72.                 fixed4 finalColor = (1 - dist1) * _Color1out + dist1  * _Color2out;
    73.  
    74.                 //Copy the alpha value from the original texture.
    75.                 finalColor.w = texColor.w;
    76.                 texColor = finalColor;
    77.  
    78.                 return texColor * IN.color;
    79.             }
    80.         ENDCG
    81.         }
    82.     }
    83. }
    84.  
    Edit: You can change line 67 to ignore alpha value in distance checking:
    Code (CSharp):
    1. fixed dist1 = distance(texColor.xyz, _ColorIn.xyz);
     
    Last edited: Nov 3, 2015
  41. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Hi guys, I also needed to swap Colors, i ended up using this solution by Daniel Branicki, complete with sample project:
    https://gamedevelopment.tutsplus.co...-dynamically-swap-a-sprites-colors--cms-25129

    The reason i switched to this shader solution is the easy achievable flash-white-hit-effect which comes with it. I have no idea of how this shader approach would impact performance compared to the one-time-only texture-preparations, but i imagine that i needed something like that anyway to achieve the hit-effect
     
  42. oanamoraru

    oanamoraru

    Joined:
    Mar 13, 2017
    Posts:
    2
    Thank you very much for posting this, I am trying to get it to work but it doesn't yet.
    For my Sprite I have set the Filter mode to Point, Wrap mode to Clamp, and checked the Read/Write Enabled Checkbox.
    However since TrueColor does not exist in current Unity versions anymore, I chose RGBA (32 bit).
    Furthermore, for the Shader I copied the code into a (Create -> Shader -> )Standard Surface Shader and attached the Material as described.
    Then I selected by hand, for the Color in slots, the colors I want to change from my in-game sprite.
    However when I run the game, nothing changes.... only the changing of the TiltColor seems to make any kind of change.
    Does anybody have an idea what the problem could be? I'd be very thankful!

    Edit1: Ok the Script is working today, it's a miracle <.< thank you anyway!

    Best regards,
    Oana

     
    Last edited: Jul 13, 2017
    CarterG81 likes this.
  43. unity_iLxAeHlWxpIIXw

    unity_iLxAeHlWxpIIXw

    Joined:
    May 7, 2018
    Posts:
    12
    Can you give the simple demo of shader? how to make shader against any image and how to take various color input and how to recolor it and what does mean unique shader or texure? kindly help me
    i want to make coloring game( color image by its color number). and through pixels it take too much time and that is why i want to try shader but i don't know about shader that how to use it for coloring?
    thanx in advance.