Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

Replacing a color on standard opaque shader with another color

Discussion in 'Shaders' started by Stone-Legion, Jul 18, 2016.

  1. Stone-Legion

    Stone-Legion

    Joined:
    Aug 16, 2013
    Posts:
    112
    I've been looking around and trying to get my head into shader coding but I can't seem to find any reference to how I can change the color of a certain pixel from one to another on a model. I get the logic of it, but I don't know the variables to access this.


    Below is what I've managed to put together so far, but how do I tease out the color in fixed4 frag to compare colors and replace?

    Code (csharp):
    1.  
    2.   fixed4 frag(v2f i) : COLOR
    3.             {
    4.                 fixed4 col = tex2D(_MainTex, i.texcoord);
    5.            //doesn't compile because wrong type of comparison.
    6.             // how to compare color?
    7.                 if (col == _Color1)
    8.                 {
    9.                     col = _Color2;
    10.                 }
    11.                 return col;
    12.             }
    13.  
    Full custom shader:

    Code (csharp):
    1.  
    2.  
    3. Shader "Custom/ColorReplaceColor"
    4.     {
    5.     Properties {
    6.         _Color ("Color", Color) = (1,1,1,1)
    7.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    8.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    9.         _Metallic ("Metallic", Range(0,1)) = 0.0
    10.  
    11.  
    12.         _Color1("Color1", Color) = (1,1,1,1)
    13.         _Color2("Color2", Color) = (1,1,1,1)
    14.     }
    15.     SubShader
    16.     {
    17.         Tags { "RenderType" = "Opaque" }
    18.         LOD 200
    19.      
    20.             CGPROGRAM
    21.                 // Physically based Standard lighting model, and enable shadows on all light types
    22.                 #pragma surface surf Standard fullforwardshadows
    23.  
    24.                 // Use shader model 3.0 target, to get nicer looking lighting
    25.                 #pragma target 3.0
    26.  
    27.                 sampler2D _MainTex;
    28.  
    29.                 struct Input {
    30.                 float2 uv_MainTex;
    31.                 };
    32.  
    33.                 half _Glossiness;
    34.                 half _Metallic;
    35.                 fixed4 _Color;
    36.  
    37.                 void surf(Input IN, inout SurfaceOutputStandard o) {
    38.                     // Albedo comes from a texture tinted by color
    39.                     fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    40.                     o.Albedo = c.rgb;
    41.                     // Metallic and smoothness come from slider variables
    42.                     o.Metallic = _Metallic;
    43.                     o.Smoothness = _Glossiness;
    44.                     o.Alpha = c.a;
    45.                 }
    46.             ENDCG
    47.         Pass{
    48.             CGPROGRAM
    49.                 #pragma vertex vert
    50.                 #pragma fragment frag
    51.  
    52.                 #include "UnityCG.cginc"
    53.  
    54.                 struct appdata_t {
    55.                 float4 vertex : POSITION;
    56.                 float2 texcoord : TEXCOORD0;
    57.             };
    58.  
    59.             struct v2f {
    60.                 float4 vertex : SV_POSITION;
    61.                 half2 texcoord : TEXCOORD0;
    62.             };
    63.  
    64.             sampler2D _MainTex;
    65.             float4 _Color1;
    66.             float4 _Color2;
    67.  
    68.             v2f vert(appdata_t v)
    69.             {
    70.                 v2f o;
    71.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    72.                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    73.                 return o;
    74.             }
    75.  
    76.             fixed4 frag(v2f i) : COLOR
    77.             {
    78.                 fixed4 col = tex2D(_MainTex, i.texcoord);
    79.                 if (col == _Color1)
    80.                 {
    81.                     col = _Color2;
    82.                 }
    83.                 return col;
    84.             }
    85.  
    86.                 ENDCG
    87.             }
    88.      
    89.     }
    90.     FallBack "Standard"
    91.  
    92. }
    93.  
    94.  
    I've found some examples here but these are all for 2d sprite texture and i'm trying to do this on 3d model. Any suggestions would be greatly appreciated.
     
    Last edited: Jul 18, 2016
  2. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    You need a mask that you will merge with the original texture color

    What you do won't work because pixel don't actually "exist" they are sampled through bilinear filtering, which mean that color don't exist in a point but between two point as a gradient, unless you disable bilinear filtering for a pixelated visual.
     
    Last edited: Jul 18, 2016
  3. Stone-Legion

    Stone-Legion

    Joined:
    Aug 16, 2013
    Posts:
    112
    Are you sure? How then would one get set colour at a given location prior to lighting etc being applied?

    Do you have any example of mask shader? I'm doing this for custom colouring of player characters.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    The pixels exist, but the exact color that appears in a pixel from the original artwork might not ever appear in the shader when it reads the texture. Reasons for this are bilinear sampling, compression, and sometimes float precision.

    Bilinear sampling is when a shader reads a texture from a specific location it's likely not exactly at the center of a pixel in the original image, but pretty much always somewhere between the center of 4 pixels. So it blends the color to be somewhere between those 4 colors, linearly interpolating between the horizontal and vertical colors, hence bilinear. You can turn this off and use point sampling but then the texturing wont look as nice.

    Compression approximates the colors in the image to reduce the file size. Image compression usually tries to make an image look "close enough" to the original that you usually won't notice something is missing or wrong when rendered, but it is very likely that it will not be the same as the original. It's entirely possible to have an image that's a single solid color that after compression is a solid slightly different color, or even a hand full of similar slightly different colors.

    Float precision can also come into play, but the other two are far bigger obstacles.

    Masks are usually done with a second texture where instead of dealing with the vagaries of trying to match a particular shade of orange exactly, a single color channel by itself is used as a mask. Is red a solid 255 (which will be 1.0 in the shader) and we should recolor, or is it 0 (0.0 in the shader) and we should ignore it, or is it 127 (not 0.5, rather ~0.2, we can get to that later) then blend between colored and not, or just test to see if red is > 127.
     
  5. Stone-Legion

    Stone-Legion

    Joined:
    Aug 16, 2013
    Posts:
    112
    Alright so I've discovered this snippet from another thread, obviously the problem here is that it doesn't take lighting into account. I've been searching all over the forums for a hint at how I might integrate an alpha tint into the standard unity 5 shader but so far my searches have not been fruitful.

    I'm really in a time crunch and would appreciate if anyone could give some direction in how to tint the alpha channel only on standard unity opaque shader.

    Code (CSharp):
    1. Shader "Tinted using -Alpha" {
    2.  
    3.     Properties
    4.     {
    5.         _Color("Tint Color", Color) = (1,1,1)
    6.         _MainTex("Texture", 2D) = "white"
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Pass
    12.         {
    13.             SetTexture[_MainTex]
    14.             {
    15.                 ConstantColor[_Color]
    16.                 Combine texture * constant
    17.             }
    18.  
    19.             SetTexture[_MainTex]
    20.             {
    21.                 Combine previous lerp(texture) texture
    22.             }
    23.         }
    24.     }
    25.  
    26. }
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    That's a fixed function shader, which is effectively deprecated.

    If you just want a shader that tints based on the main texture's alpha that's really easy.

    Code (CSharp):
    1. Shader "Custom/ColorReplaceColor"
    2.     {
    3.     Properties {
    4.         _Color ("Color", Color) = (1,1,1,1)
    5.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.         _Color1("Color1", Color) = (1,1,1,1)
    9.         _Color2("Color2", Color) = (1,1,1,1)
    10.     }
    11.     SubShader
    12.     {
    13.         Tags { "RenderType" = "Opaque" }
    14.         LOD 200
    15.    
    16.             CGPROGRAM
    17.                 // Physically based Standard lighting model, and enable shadows on all light types
    18.                 #pragma surface surf Standard fullforwardshadows
    19.                 // Use shader model 3.0 target, to get nicer looking lighting
    20.                 #pragma target 3.0
    21.                 sampler2D _MainTex;
    22.                 struct Input {
    23.                 float2 uv_MainTex;
    24.                 };
    25.                 half _Glossiness;
    26.                 half _Metallic;
    27.                 fixed4 _Color;
    28.                 void surf(Input IN, inout SurfaceOutputStandard o) {
    29.                     // Albedo comes from a texture tinted by color masked by the texture's alpha
    30.                     fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    31.                     o.Albedo = lerp(c.rgb, c.rgb * _Color.rgb, c.a);
    32.                     // Metallic and smoothness come from slider variables
    33.                     o.Metallic = _Metallic;
    34.                     o.Smoothness = _Glossiness;
    35.                 }
    36.             ENDCG
    37.    
    38.     }
    39.     FallBack "Standard"
    40. }
    41.  
     
  7. Stone-Legion

    Stone-Legion

    Joined:
    Aug 16, 2013
    Posts:
    112
    Thank you so much, this is great base for me to learn from.

    I've just spent the last few hours watching unity shader tutorials, I feel so excited to be learning shader code. Def. going t bury my head in it.

    I've modified your posted code a bit more to add a second alpha mask for the hair, it works but just wondering if you might take a quick look to see if it is right/wrong since I feel a bit funny using a second full texture with alpha mask.



    Code (CSharp):
    1. Shader "Custom/ColorReplaceColor2"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1,1,1,1)
    6.         _HairTint("HairTint", Color) = (1,1,1,1)
    7.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    8.         _MainTexHairMask("Hair with Alpha Mask", 2D) = "white" {}
    9.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    10.         _Metallic("Metallic", Range(0,1)) = 0.0
    11.     }
    12.     SubShader
    13.     {
    14.         Tags{ "RenderType" = "Opaque" }
    15.         LOD 200
    16.  
    17.         CGPROGRAM
    18.         // Physically based Standard lighting model, and enable shadows on all light types
    19.         #pragma surface surf Standard fullforwardshadows
    20.         // Use shader model 3.0 target, to get nicer looking lighting
    21.         #pragma target 3.0
    22.         sampler2D _MainTex;
    23.         sampler2D _MainTexHairMask;
    24.  
    25.         struct Input {
    26.             float2 uv_MainTex;
    27.             float2 uv_MainTexHairMask;
    28.         };
    29.  
    30.         half _Glossiness;
    31.         half _Metallic;
    32.         fixed4 _Color;
    33.         fixed4 _HairTint;
    34.  
    35.         void surf(Input IN, inout SurfaceOutputStandard o)
    36.         {
    37.             // Albedo comes from a texture tinted by color masked by the texture's alpha
    38.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    39.             fixed4 c2 = tex2D(_MainTexHairMask, IN.uv_MainTexHairMask);
    40.             o.Albedo = lerp(c.rgb, c.rgb * _Color.rgb, c.a);
    41.             o.Albedo += lerp(c2.rgb, c.rgb* _HairTint.rgb, c2.a);
    42.             // Metallic and smoothness come from slider variables
    43.             o.Metallic = _Metallic;
    44.             o.Smoothness = _Glossiness;
    45.         }
    46.         ENDCG
    47.  
    48.     }
    49.     FallBack "Standard"
    50. }
     
    Last edited: Jul 19, 2016
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    Hopefully you noticed this thread:
    http://forum.unity3d.com/threads/as-a-newcomer-what-i-should-do-for-shader.415751/

    It has a couple links to things that are invaluable for learning shaders. The first link to the Unite video I would actually skip initially. I highly recommend the alanzucconi.com "A Gentle Introduction" link and the Shaders 101 first. The Unite video talks very briefly about shader basics before kind of delving headlong into more Unity specific lighting related stuff.

    As for what you're doing in your shader; yes you'll want to use two textures but you probably do not want to use the alpha from the texture once you need more than one mask. Textures are usually three or four color channels, RGB (Red Green Blue) and sometimes RGBA (an additional Alpha) and each can be used individually. The way that textures are compressed for real time the alpha actually doubles the size of the texture both on disk and within the GPU memory, so if you use two RGB textures without alpha you can get 3 masks at the same memory cost as a single texture with RGBA. If you only need one mask you would still need two textures, or one texture with alpha, and there's some performance savings in using a single texture instead of two.

    So my suggestion would look like this:
    Code (CSharp):
    1.             // Albedo comes from a texture tinted by color masked by a second texture
    2.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    3.             // reuse the main texture's UVs for the mask texture
    4.             fixed4 mask = tex2D(_MaskTex, IN.uv_MainTex);
    5.             // mask tex's red is the mask for the first tint
    6.             o.Albedo = lerp(c.rgb, c.rgb * _Color.rgb, mask.r);
    7.             // green is the mask for the second tint
    8.             o.Albedo = lerp(o.Albedo, c.rgb * _HairTint.rgb, mask.g);
    9.             // Metallic and smoothness come from slider variables
    10.             o.Metallic = _Metallic;
    11.             o.Smoothness = _Glossiness;
    Some additional notes not commented in the above, I don't do "o.Albedo += ..." but rather lerp between the previously set o.Albedo and the alternatively tinted texture. The benefit over += is you don't get over brightening of the texture or have to black out the texture to prevent over brightening. A side effect is that second mask will override the first one so you can't have an overlap that's been tinted twice. Usually this is what you want to have happen anyways so it means you can (and actually want to be) a little sloppy where the two masks overlap and let the red channel mask overlap some with the green channel mask.
     
    Stone-Legion likes this.
  9. Stone-Legion

    Stone-Legion

    Joined:
    Aug 16, 2013
    Posts:
    112
    Thanks so much for your help and advice. I really appreciate it and I've learned a lot today and now spending my evening watching more tutorials. I've modified the code you shared to also create a third mask layer for blue - your help was perfect.

    You're a gentleman and a scholar.
     
    Last edited: Jul 19, 2016
  10. HypoXic5665

    HypoXic5665

    Joined:
    Sep 10, 2012
    Posts:
    12
    Super helpful thread. Unfortunately I am a super shader scrub. This shader does pretty much exactly what I want, however I do not want the output color to blend the with the main texture color. I would like the output color to be the true color defined over the masked areas.

    The only solution that I have come up with is to something like.

    Code (CSharp):
    1. if (mask.r >= 1)
    2.         {
    3.             o.Albedo = _Color.rgb;
    4.         }
    5.         else {
    6.             o.Albedo = c;
    7.         }
    I am sure that this is very inefficient and it results in the masked areas becoming very muddy/pixelated.

    I feel like this should be fairly straightforward. Any info you have to help out would be incredible!

    Thanks,
     
  11. CaptainBinky

    CaptainBinky

    Joined:
    Aug 5, 2017
    Posts:
    14
    The lerp as suggested is really the best way to go imo.

    o.Albedo = lerp(_Color, c, mask.r);

    The important thing, bearing in mind that when mask.r is one, the colour will be 'c', when it's zero it'll be '_Color' and it'll blend between the two is that you can control the way that blend happens by modifying mask.r

    For example, if you want a sharper blend:

    o.Albedo = lerp(_Color, c, pow(mask.r, 2));

    Or a kind of ease-in, ease-out:

    o.Albedo = lerp(_Color, c, 1 - (cos(mask.r * PI) * 0.5 + 0.5));

    Where PI is defined as 3.14159265

    So this way you can control the blend so that it's more to your taste. If it's absolutely essential that there's no blending what-so-ever then use step:

    o.Albedo = lerp(_Color, c, step(mask.r, 1));

    which will do, essentially, the same as your if statement without an if - parameterise that '1' into a shader input between 0 and 1, and you have a threshold slider to play with. That said, if you don't like the results of the if you won't like the result of step either ;) As far as the efficiency goes, I wouldn't worry too much about using an if in that way ;)

    edit: It might help, HypoXic, if you posted an image showing what exactly it is about your results which you don't like, and maybe an image mocking up what it is specifically which you desire.
     
    Last edited: Oct 2, 2017
  12. HypoXic5665

    HypoXic5665

    Joined:
    Sep 10, 2012
    Posts:
    12
    Thank you CaptainBinky, I will try this and post some images if needed when get a chance to look at my project!
     
  13. HypoXic5665

    HypoXic5665

    Joined:
    Sep 10, 2012
    Posts:
    12
    @CaptianBinky: Your suggestion works great for one mask changing color. I am using your example:

    o.Albedo = lerp(c, _Color, pow(mask.r, 2));

    I like that it gives me the option to modify the intensity of the color (although it is not needed at this time, flexibility is good).

    Now, for the need to modify multiple colors from the masking texture I have come up with the following shader to allow customization of each red, green, and blue input on the masking texture.

    Code (CSharp):
    1. Shader "Custom/ColorMaskRGB"
    2. {
    3.     Properties
    4.     {
    5.         _RedMaskColor("Color Mask Red", Color) = (1,0,0,1)
    6.         _GreenMaskColor("Color Mask Green", Color) = (0,1,0,1)
    7.         _BlueMaskColor("Color Mask Blue", Color) = (0,0,1,1)
    8.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    9.         _MaskTex("Mask (RGB)", 2D) = "white" {}
    10.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    11.         _Metallic("Metallic", Range(0,1)) = 0.0
    12.     }
    13.         SubShader
    14.     {
    15.         Tags{ "RenderType" = "Opaque" }
    16.         LOD 200
    17.  
    18.         CGPROGRAM
    19.         // Physically based Standard lighting model, and enable shadows on all light types
    20. #pragma surface surf Standard fullforwardshadows
    21.         // Use shader model 3.0 target, to get nicer looking lighting
    22. #pragma target 3.0
    23.     sampler2D _MainTex;
    24.     sampler2D _MaskTex;
    25.  
    26.     struct Input {
    27.         float2 uv_MainTex;
    28.         float2 uv_MaskTex;
    29.     };
    30.  
    31.     half _Glossiness;
    32.     half _Metallic;
    33.     fixed4 _RedMaskColor;
    34.     fixed4 _GreenMaskColor;
    35.     fixed4 _BlueMaskColor;
    36.  
    37.     void surf(Input IN, inout SurfaceOutputStandard o)
    38.     {
    39.         // Albedo comes from a texture tinted by color masked by a second texture
    40.         fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    41.         // reuse the main texture's UVs for the mask texture
    42.         fixed4 mask = tex2D(_MaskTex, IN.uv_MainTex);
    43.  
    44.         if (mask.r > 0)
    45.         {
    46.             o.Albedo = lerp(c, _RedMaskColor, pow(mask.r, 2));
    47.         }
    48.         else if (mask.g > 0)
    49.         {
    50.             o.Albedo = lerp(c, _GreenMaskColor, pow(mask.g, 2));
    51.         }
    52.         else if (mask.b > 0)
    53.         {
    54.             o.Albedo = lerp(c, _BlueMaskColor, pow(mask.b, 2));
    55.         }
    56.         else
    57.         {
    58.             o.Albedo = c;
    59.         }
    60.  
    61.         // Metallic and smoothness come from slider variables
    62.         o.Metallic = _Metallic;
    63.         o.Smoothness = _Glossiness;
    64.     }
    65.     ENDCG
    66.  
    67.     }
    68.         FallBack "Standard"
    69. }
    Modifying the If statements to check for a mask.x > 0 (instead of mask.x >= 1) has solved the issue of my texture becoming "muddy".

    Is the shader above an effective solution in your opinion? For the time being all that I need is to let the user customize the whole red, green, and blue colors on a masking texture over an object. I am beginning to see how this can become quite robust and intricate. However a simple solution will do for this project as I learn more.

    Thanks so much for your input. It has been greatly helpful!

    Attached are files of the base and mask texture as well as the result in the editor.
     

    Attached Files:

    Kimmander likes this.
  14. CaptainBinky

    CaptainBinky

    Joined:
    Aug 5, 2017
    Posts:
    14
    Hi HypoXic,

    I'd argue that if the shader does pretty much you want, then it is an effective solution by that virtue alone :) If I were to comment though, it would be to question why the ifs are necessary at all, why not instead just do:

    Code (CSharp):
    1. c = lerp(c, _RedMaskColor, pow(mask.r, 2));
    2. c = lerp(c, _GreenMaskColor, pow(mask.g, 2));
    3. c = lerp(c, _BlueMaskColor, pow(mask.b, 2));
    4. o.Albedo = c;
    given that if the mask is zero then the lerp will simply equate to "c = c" which obviously does nothing. That said, if you want to ensure that if mask.r is non-zero not to do any blending with the green or blue mask - one or the other but never both - then as you've written it does accomplish that :)

    edit: In terms of combining your masks into the RGBA channels of your texture, this is sensible yes :)
     
    Last edited: Oct 3, 2017
  15. HypoXic5665

    HypoXic5665

    Joined:
    Sep 10, 2012
    Posts:
    12
    Ahh I see. I had:

    o.Albedo = lerp(c, _xMaskColor, pow(mask.r, 2));

    For each color intended and It was only showing the last one calculated. Silly mistake on my part.

    Thank you for the comments. You have been super helpful!