Search Unity

Shader lerping colors and color values above 1

Discussion in 'Shaders' started by ryanbmcm, Apr 23, 2016.

  1. ryanbmcm

    ryanbmcm

    Joined:
    Oct 6, 2015
    Posts:
    4
    I have source images where each pixel is one of exactly 4 colors:

    - red = (1,0,0)
    - green = (0,1,0)
    - blue = (0,0,1)
    - black = (0,0,0)

    I'm trying to modify Unity's default sprite shader to use those colors (besides black) as a key to change to other colors. For example, any pixel that's red should become _RedColor. My first attempt was:

    Code (CSharp):
    1. fixed4 _RedColor;
    2. fixed4 _BlueColor;
    3. fixed4 _GreenColor;
    4.  
    5. fixed4 frag(v2f IN) : SV_Target {
    6.     fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
    7.  
    8.     fixed4 modColor;
    9.     modColor.rgb = _RedColor.rgb*step(1, c.r);
    10.     modColor.rgb = _BlueColor.rgb*step(1, c.b);
    11.     modColor.rgb = _GreenColor.rgb*step(1, c.g);
    12.  
    13.     modColor.rgb *= c.a;
    14.     return modColor;
    15. }
    This worked. The problem was it was very jagged. In exporting the image and being compressed in Unity I think some pixels had multiple colors but the shader simply would set the color to the last matching color.

    So I tried to modify it to not reset the color if the next color matches, but instead blend between any colors present in the pixel weighted by their contribution. Here was my second attempt:

    Code (csharp):
    1. fixed4 frag(v2f IN) : SV_Target {
    2.     fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
    3.        
    4.     float total = c.r + c.b + c.g;
    5.  
    6.     // we set the total to 1 if it's 0 so that we don't divide by zero when the color is black
    7.     total += step(total, 0);
    8.  
    9.     c.rgb = _RedColor.rgb*c.r + _BlueColor.rgb*c.b + _GreenColor.rgb*c.g;
    10.     c.rgb /= total;
    11.  
    12.     c.rgb *= a;
    13.     return c;
    14. }
    This also worked and the jaggedness went away everywhere except the border of black and nonblack colors. So then I tried getting rid of the total and it was perfect:

    Code (csharp):
    1. fixed4 frag(v2f IN) : SV_Target {
    2.     fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
    3.  
    4.     c.rgb = _RedColor.rgb*c.r + _BlueColor.rgb*c.b + _GreenColor.rgb*c.g;
    5.  
    6.     c.rgb *= c.a;
    7.     return c;
    8. }
    It's perfectly blended between any of the pixels. But I don't get why. I was dividing by the total so that the color values wouldn't go above 1 and it would blend between them. For example if a pixel had both red and blue present it would be adding the 50% of _RedColor's rgb value and 50% of the _BlueColor's rgb value. But when I didn't divide by the total it appears nothing changed besides blending between black and other colors.

    This implies to me that I never had any pixels that actually had more than one nonzero color component to them (or else I would be getting white or weird colors at those pixels). But if that's the case, why did switching from the first step function approach to a blend approach even do anything?

    This is my first foray into shaders so it's entirely possible I'm doing something terribly, but I just want to wrap my head around what's going on and why. So I definitely appreciate any help/ideas.
     
  2. smd863

    smd863

    Joined:
    Jan 26, 2014
    Posts:
    292
    It works because all your pixel values are close to 1 or 0 except where compression muddies the edges. So you would be adding 99% of your red color, and 1% of your blue color (divided by 1 for no change). You should set compression to 16-bit, use your last version of the shader, and you won't get any compression artifacts at all.

    That's also why your first shader failed. A red value of 99% due to compression would step to 0, not 1. You could probably keep compression on if you used a slightly more lenient cutoff for your values. Using 0.99 as a cutoff would probably work better.
     
    ryanbmcm likes this.
  3. ryanbmcm

    ryanbmcm

    Joined:
    Oct 6, 2015
    Posts:
    4
    Ahhhh. That makes perfect sense.

    I was assuming compression would cause some pixels to be (0,1,1) for example but instead you're saying it blends to something like (0, .99, .1) so it still adds to 1 so I don't need to divide by the total.

    My last question would be which of the versions of the shaders is more performant or are they both pretty much the same?
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    The step function is fairly heavy and you don't need it to do what you want.
     
  5. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    181
    is the step() function really heavy? do you have any data about this?
    we are using step() in pixel shader for a mobile game, so I am a bit worry about this.
     
  6. bart_the_13th

    bart_the_13th

    Joined:
    Jan 16, 2012
    Posts:
    498
    I'm pretty sure it's not as heavy as using if or alpha cutout shader... Quite fast if I may say...
     
  7. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    From: http://http.developer.nvidia.com/Cg/step.html

    Code (cg):
    1.  
    2. float3 step(float3 a, float3 x)
    3. {
    4.      return x >= a;
    5. }
    6.  
    That's a branch. It's simple so it probably won't kill you (and maybe has some hardware optimizations? dunno), you'll have to profile on device to know for sure how expensive it is.

    It sounds like you don't need step in this case at all but here's an alternative:

    Code (cg):
    1. modColor.rgb= _RedColor.rgb*step(1, c.r);
    ...could be...

    Code (cg):
    1.  
    2. //subtract one so we only get values above 1
    3. float mask = c.r -1;
    4.  
    5. //multiply by some big number so we get white above 0
    6. mask *= 900.0;
    7.  
    8. //saturate it for sanity
    9. mask = saturate(mask);
    10.  
    11. modColor.rgb = _RedColor.rgb * mask;
    12.  
    Extra whitespace for clarity.
     
    colin299 likes this.