Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Simple Color Swap

Discussion in 'General Graphics' started by gennen, Oct 20, 2016.

  1. gennen

    gennen

    Joined:
    Feb 5, 2013
    Posts:
    18
    A while back I tried to figure out how to do simple per-entity color swaps for sprites and ran into repeated failures, using both sprite-editing techniques and shaders. I'm hoping that someone out there can point me in a more fruitful direction. What I'd like to do is have two reserved RGB values that I can use as primary and secondary colors in sprites and through code overwrite those colors for a particular game object. Basically just making a face that uses some color for skin color and another for eveys and then have a FaceColor script that overwrites those for each smiley face object. Help?
     
  2. PeteUnity3D

    PeteUnity3D

    Unity Technologies

    Joined:
    Jan 4, 2016
    Posts:
    68
    Do you mean an RGB mask? Similar to:
     
    JamesArndt likes this.
  3. gennen

    gennen

    Joined:
    Feb 5, 2013
    Posts:
    18
    Pete, you're a hero! Thanks a ton. I need to learn enough about shaders to make this a Sprite shader and to modify those values per-object, but this is definitely the right direction. You're a life-saver.
     
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Hi Gennen,

    it looks you are quite happy with the shader approach. If you want to do this on simple sprite objects, I suggest you take a look at. https://docs.unity3d.com/ScriptReference/Texture2D.html

    Take a look at the GetPixels / SetPixels functions. This way, you do not need to draw masks or anything and can simply swap out colors based on their RGB value. You basically have a color array in which you can swap out color values.
     
    theANMATOR2b likes this.
  5. gennen

    gennen

    Joined:
    Feb 5, 2013
    Posts:
    18
    sngdan - I suspect that Get/SetPixel are a better fit for me, but when I last tried it I ran into all sorts of troubles revolving around not being able to set or save the texture. Various permissions stuff that I couldn't find documentation on. What I would most love to do is write a script that basically says foreach pixel in an object's sprite renderer, if that pixel's color = x, replace it with public Color y. Does that seem like a thing that should be easy/possible.?
     
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Yes, this is easy. Based on your description, the two things that might have troubled you, which are easy to resolve.
    1. Import settings: I assume you import as Sprite. You have to change this to Advanced. Familiarize yourself with the various settings in advanced, but the one you need to change is read/write enabled - tick this. You also will need to pay some attention to the mipmap (easiest is off), filter mode (easiest is Point) and color format (easiest is an uncompressed truecolor like ARGB) --- when I write easiest, it is based on my assumption what you are trying to do
    2. Texture2D: you have to apply the changes you did with set pixels once you are done.
    Once you read the pixels into a Color / Color32 array, you just loop through it and compare the RGB values (Alpha - likely not relevant for you) and swap out the ones in question.

    The Unity manual page I linked earlier basically has this script, you just need to change the line that currently sets the white / gray color with the color swap you want.
     
    PeteUnity3D and theANMATOR2b like this.
  7. PeteUnity3D

    PeteUnity3D

    Unity Technologies

    Joined:
    Jan 4, 2016
    Posts:
    68
  8. gennen

    gennen

    Joined:
    Feb 5, 2013
    Posts:
    18
    You guys are awesome. I have it 90% working, once I used sngdan's settings and the script from Pete's thread modified to set a public Sprite variable instead of one in a current Sprite Renderer. The 10% is that the colors aren't showing up correctly for reasons that I'm guessing have to do with color formatting. If I SetPixel to something default like Color.cyan it works perfectly, but if I SetPixel to "public Color SomeColorISetThroughTheInspector" they all come out grey.
     
  9. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Can you post the code you use to read the color from the inspector (public variable) and how you write it (set pixel). Are you working with color or color32 (not that it makes much of a difference)
     
  10. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Probably you didn't set the alpha value correctly in the public color.

    --Eric
     
  11. gennen

    gennen

    Joined:
    Feb 5, 2013
    Posts:
    18
    It works! Eric, you're a hero as well. Why do public Colors default to an alpha of 0? That seems weird. In case anyone stumbles across this thread, I'll post my code, which is very lightly modified from that other thread. I made this because I want to modify a character's image (skin tone, hair, eyes) in data so I can use it in a variety of things, like HUD elements generated at run time. sngdan's import settings + Pete's link + Eric's note about public colors = success.

    Code (CSharp):
    1.     //CopiedTexture is the original Texture  which you want to copy.
    2.     public Texture2D CopyTexture2D(Texture2D copiedTexture)
    3.     {
    4.         //Create a new Texture2D, which will be the copy.
    5.         Texture2D texture = new Texture2D(copiedTexture.width, copiedTexture.height);
    6.         //Choose your filtermode and wrapmode here.
    7.         texture.filterMode = FilterMode.Point;
    8.         texture.wrapMode = TextureWrapMode.Clamp;
    9.  
    10.         int y = 0;
    11.         while (y < texture.height)
    12.         {
    13.             int x = 0;
    14.             while (x < texture.width)
    15.             {
    16.                 //INSERT YOUR LOGIC HERE
    17.                 if (copiedTexture.GetPixel(x, y) == Color.green)
    18.                 {
    19.                     texture.SetPixel(x, y, Primary);
    20.                 }
    21.                 else if (copiedTexture.GetPixel(x, y) == Color.blue)
    22.                 {
    23.                     texture.SetPixel(x, y, Secondary);
    24.                 }
    25.                 else if (copiedTexture.GetPixel(x, y) == Color.red)
    26.                 {
    27.                     texture.SetPixel(x, y, Tertiary);
    28.                 }
    29.                 else
    30.                 {
    31.                     texture.SetPixel(x, y, copiedTexture.GetPixel(x, y));
    32.                 }
    33.                 ++x;
    34.             }
    35.             ++y;
    36.         }
    37.  
    38.         //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.
    39.         texture.Apply();
    40.  
    41.         //Return the variable, so you have it to assign to a permanent variable and so you can use it.
    42.         return texture;
    43.     }
    44.  
    45.     public void UpdateCharacterTexture()
    46.     {
    47.         //This calls the copy texture function, and copies it. The variable characterTextures2D is a Texture2D which is now the returned newly copied Texture2D.
    48.         CharacterTexture2D = CopyTexture2D(gameObject.GetComponent<Phenotype>().Picture.texture);
    49.  
    50.         //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.
    51.         Sprite sprite = Sprite.Create(CharacterTexture2D, gameObject.GetComponent<Phenotype>().Picture.rect, new Vector2(0, 1));
    52.         GetComponent<Phenotype>().Picture = sprite;
    53.  
    54.     }
     
  12. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Rather than calling GetPixel repeatedly, you could speed that up by having a variable of type Color (name it pixColor), and putting "pixColor = copiedTexture.GetPixel(x, y)" at the top of the "while(x < texture.width)" loop, and changing all cases of copiedTexture.GetPixel to pixColor.

    --Eric
     
  13. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Yep, that's what I meant by creating and looping through a color array. Basically use getpixels and setPixels. But it's essencially the same thing, just faster according to the manual. It all depends on how important it is. I.e. If it only happens once at start, it does not really matter much, if it's every second (say through an automated color cycle) in game, I would rather do it in a shader...(same technique, not with masks).

    Anyway, well done @gennen for figuring it out, much better for learning than just having someone past the solution (I almost did in my first post, but it's always messy to do it from mobile, so I did not)