Search Unity

Color Replacement Shader

Discussion in 'Shaders' started by monkeytrumpet, Mar 29, 2011.

  1. monkeytrumpet

    monkeytrumpet

    Joined:
    Mar 29, 2011
    Posts:
    4
    Hi Folks,

    I've been having a look around and trying to find a good starting point to make a color replacement shader - but haven't had any luck to date with either google or forum searches.

    Basically what I'm trying to do is start with a grayscale sprite and then be able to replace each individual shade of gray with a different color. I'm using spritemanager 2 - so my theory is that with grayscale sprites I can then make different color schemes etc.

    If anybody can give me some prompting in the right direction on this I'd appreciate it. :)

    Cheers,

    Matt
     
  2. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    This will swap a greyscale value for the colour at that value on another pixel ramp.

    It's important to ensure that the ramp texture is 256 pixels long and that you apply the following settings in the inspector, otherwise you risk getting fuzzy results;
    Filter Mode : Point
    Wrap Mode : Clamp
    Format : Truecolor

    Or;
    Filter Mode : Point
    Wrap Mode : Clamp
    Texture Type : Advanced
    Generate Mip Maps : Unticked/Off

    e.g.

    This texture and this ramp give this result;

    (greyscale texture to be replaced)


    (colour ramp - darker colours swapped with more left hand side colour, brighter colours swapped with more right hand side colour)


    (left : greyscale, right : colour replacement)


    Code (csharp):
    1.  
    2. Shader "ColourReplacement" {
    3.     Properties {
    4.         _MainTex ("Greyscale (R) Alpha (A)", 2D) = "white" {}
    5.         _ColorRamp ("Colour Palette", 2D) = "gray" {}
    6.     }
    7.  
    8.     SubShader {
    9.         Pass {
    10.             Name "ColorReplacement"
    11.            
    12.             CGPROGRAM
    13.                 #pragma vertex vert
    14.                 #pragma fragment frag
    15.                 #pragma fragmentoption ARB_precision_hint_fastest
    16.                 #include "UnityCG.cginc"
    17.                
    18.                 struct v2f
    19.                 {
    20.                     float4  pos : SV_POSITION;
    21.                     float2  uv : TEXCOORD0;
    22.                 };
    23.  
    24.                 v2f vert (appdata_tan v)
    25.                 {
    26.                     v2f o;
    27.                     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    28.                     o.uv = v.texcoord.xy;
    29.                     return o;
    30.                 }
    31.                
    32.                 sampler2D _MainTex;
    33.                 sampler2D _ColorRamp;
    34.  
    35.                 float4 frag(v2f i) : COLOR
    36.                 {
    37.                 // SURFACE COLOUR
    38.                     float greyscale = tex2D(_MainTex, i.uv).r;
    39.                
    40.                 // RESULT
    41.                     float4 result;
    42.                     result.rgb = tex2D(_ColorRamp, float2(greyscale, 0.5)).rgb;
    43.                     result.a = tex2D(_MainTex, i.uv).a;
    44.                     return result;
    45.                 }
    46.             ENDCG
    47.         }
    48.     }
    49.     Fallback
    50.  
    Hope that's somewhere close to where you want to get to.
     
    jister and mgear like this.
  3. monkeytrumpet

    monkeytrumpet

    Joined:
    Mar 29, 2011
    Posts:
    4
    Hey Farfarer,

    That comes close to what I want to do in terms of replacing the colours, but what I was really hoping for was the ability to select the replacement colour programmatically - with the above solution if I wanted to get all of the different replacement combinations for 255 different shades of gray I would need a prohibitive number of replacement colour ramps.

    Is it possible to have 255 replacement colours that can be modified programmatically where each replacement colour replaces one shade of gray?

    Thanks again for your help!

    Cheers,

    Matt
     
  4. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    It's technically possible, colour palettes are evidently in use in games.

    Not sure how it'd be done via a shader, though. Sorry :/

    Some sort of writeable texture that you could save/load palette info into?
     
  5. monkeytrumpet

    monkeytrumpet

    Joined:
    Mar 29, 2011
    Posts:
    4
    Hey Farfarer,

    Turns out there was a fairly easy solution to doing it the way you suggested - programmatically create the "colour ramp" texture :)

    Code (csharp):
    1. public class PaletteGenerator : MonoBehaviour {
    2. [INDENT]public Color[] colourArray = new Color[256];[/INDENT]
    3.    
    4.     public void Awake()
    5.     {
    6.         Texture2D colourPalette = new Texture2D(256, 10, TextureFormat.ARGB32, false);
    7.        
    8.         for(int x = 0; x < 256; x++){
    9.             for(int y = 0; y < 10; y++){
    10.                 colourPalette.SetPixel(x,y,colourArray[x]);
    11.             }
    12.         }
    13.         colourPalette.filterMode = FilterMode.Point;
    14.         colourPalette.wrapMode = TextureWrapMode.Clamp;
    15.         colourPalette.Apply();
    16.         renderer.material.SetTexture("_ColorRamp",colourPalette);
    17.     }
    18. }
    19.  
    This provides a 256 x 10 colour ramp which applies itself to the shader.

    So - that part is sorted! :)

    I have a follow up question - I'm battling a little with understanding your original shader - but what I was wondering is can the complexity of the colour ramp lookup be increased such that it uses the red and green elements of the original texture to look up the colour ramp?

    e.g. the colour ramp is 256 x 256 pixels, and colour (R : 0, G : 0, B : 0, A : 0) looks up the top left pixel of the ramp, and colour (R : 255, G : 255, B : 0, A : 0) looks up the bottom right pixel of the ramp (so that it's a colour map rather than a colour ramp? This would give the opportunity of a 65535 palette size rather than 256 colours - and everybody loves more colours!

    Thanks again for your help :)

    Cheers,

    Matt
     
  6. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Yeah, that's possible.

    I was just working off the assumption that there are only 256 shades of grey representable in a texture so you only needed a 256 pixel wide texture for a comprehensive lookup.

    Replacing the fragment shader part of the original one I posted with this will use the R and G channels as the UV coordinates of the palette look-up. So more red = towards right hand side, more green = towards top.
    Code (csharp):
    1.  
    2.                 float4 frag(v2f i) : COLOR
    3.                 {
    4.                 // SURFACE COLOUR
    5.                     float2 greyscale = tex2D(_MainTex, i.uv).rg;
    6.                
    7.                 // RESULT
    8.                     float4 result;
    9.                     result.rgb = tex2D(_ColorRamp, float2(greyscale.x, greyscale.y)).rgb;
    10.                     result.a = tex2D(_MainTex, i.uv).a;
    11.                     return result;
    12.                 }
    13.  
    Also, I'd make your generated texture height a power of 2 (e.g. 16 or 32 or even 256) rather than 10. Textures like to be powers of two :)
     
  7. alyons

    alyons

    Joined:
    Nov 8, 2010
    Posts:
    8
    So I've been trying to write this functionality into a variation of the standard Unity particle shaders. I'm having trouble using the _TintColor property. I think this is what allows the Particle animator to change the material's color/opacity. Any ideas?
    Thanks in advance.
     
  8. alyons

    alyons

    Joined:
    Nov 8, 2010
    Posts:
    8
    After some trial and error I figured it out. If anybody is interested in seeing the results feel free to let me know.
     
  9. Danny_HoW

    Danny_HoW

    Joined:
    May 4, 2012
    Posts:
    47
    Id love to see a final version of this if there is one please, trying to do a very similar thing
     
  10. roberto_sc

    roberto_sc

    Joined:
    Dec 13, 2010
    Posts:
    144
    I was using Unity's Unlit Transparent shader for a material that have some "holes" in it. But I switched for this script and made this modification:

    Code (csharp):
    1.  
    2.         // RESULT
    3.                     float4 result;
    4.                     result.a = tex2D(_MainTex, i.uv).a;
    5.                     if (result.a < 0.5)
    6.                          result.rgb = tex2D(_MainTex, i.uv).rgb;
    7.                    else
    8.                          result.rgb = tex2D(_ColorRamp, float2(greyscale, 0.5)).rgb;
    9.                     return result;
    10.  
    This way I'm keeping original values in transparent pixels.

    But the problem is that when I do this I lose the transparency effect. So how can I implement transparency in this shader, or how can I use both shaders?
     
  11. drudiverse

    drudiverse

    Joined:
    May 16, 2013
    Posts:
    218
    I'd strongly recommend using a CG HSVtoRGB shader function, they are awesome... it gives you natural color control like a painter instead of control like a crt monitor, which you arent presumably!

    This is a way to make 1 float value. i.e. Grey, into 1000 ds of combinations of color and brightness and saturation etc:

    int rand = a global color randomizer that you can control by script/ by a crossfader etc.
    float h = perlin(grey,23.3*rand)
    float s = perlin(grey,54,4*rand)
    float v = perlin(grey,12.6*rand)

    Return float4 HSVtoRGB(h,s,v);

    result is AWESOME COLOR RANDOMIZATION! it's not natural but it uses natural color gradients and it looks organic and controlleably irridescent / pastel parameters.

    For perlin, you can use this function, it is a fast zig zag version of perlin.
    function zig ( xx : float ): float{ //lfo nz -1,1
    xx= xx+32;
    var x0 = Mathf.Floor(xx);
    var x1 = x0+1;
    var v0 = (Mathf.Sin (x0*.014686)*31718.927)%1;
    var v1 = (Mathf.Sin (x1*.014686)*31718.927)%1;
    return Mathf.Lerp( v0 , v1 , (xx)%1 )*2-1;
    }