Search Unity

Unlit Diffuse with Tinted Color Mask

Discussion in 'Shaders' started by MaRTiDoRe, Apr 5, 2020.

  1. MaRTiDoRe

    MaRTiDoRe

    Joined:
    Jul 4, 2011
    Posts:
    41
    Hello guys,

    I'm developing a game and I'm quite stuck with a shader. my Shader knowledge is close to zero, therefore I need to use the Trial and Error method, which is slow and painful.

    Maybe someone experienced could help me with this.

    I need an Unlit shader that uses a Diffuse texture (until here, nothing special), but I also need it to tint the texture with a color, given a mask.

    So, with a white texture as a diffuse, a mask that covers/masks half of the texture, and with Green selected as color in the shader, the result should be, half of the object white and the other half green (tinted the masked part).

    Does it make sense?

    The mask can be anything. Black&White or Alpha or whatever it needs to be. It basically needs to values, 0 for no tint, 1 for tint.

    Any help would be really appreciated.

    The Shader doesn't need to cast or receive shadows. It's for a mobile game and we trying to keep it simple/optimized.

    Thanks a lot!!

    P.S: Oh, and if the shader support transparency, that would be awesome too. I would like to be able to make the object semi-transparent by modifying the Alpha value of the MainTexture (or the mask, whaterver works as long as I can modify the transparency). It's not essential but it would be cool
     
    Last edited: Apr 5, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Generally people here aren't going to just write a shader for you, especially one as basic as this where a search online will probably get you a number of shaders that are very close. Make an effort to write your own shader, ask questions when you get stuck, and we'll happily help.
     
  3. MaRTiDoRe

    MaRTiDoRe

    Joined:
    Jul 4, 2011
    Posts:
    41
    Hello bgolus

    I totally get your point. I promise I wouldn't be here asking for some guidance/help if I could do it myself.

    I'm sorry I didn´t explain that I have been bashing my head againts a wall for 2 days now, trying to get it working myself. I'm not the type of user that wants to save time/effort by asking other people to work for me. I only come here and ask for help when I have tried everything and I'm sill stuck and frustrated

    Some links I have researched:

    https://github.com/kimsama/Unity-NGUIExtension/blob/master/Assets/NGUI-Extension/Resources/Shaders/Transparent Colored Masked.shader

    https://forum.unity.com/threads/col...d-black-area-when-selecting-grey-area.545539/

    https://forum.unity.com/threads/sol...sparent-cutout-soft-edge-unlit-shader.289856/

    https://bensilvis.com/unity3d-unlit-alpha-mask-shader/

    The thing is, everytime I find something related to masks, they are almost always used for Alphas, and I have been unable to adapt it to color tint. Again, Shaders are totally out of my range of knowledge.
     
    Last edited: Apr 6, 2020
  4. MaRTiDoRe

    MaRTiDoRe

    Joined:
    Jul 4, 2011
    Posts:
    41
    This is what I have achieved so far:

    Code (CSharp):
    1.  
    2. Shader "Unlit/TransparentTextureMask" {
    3.     Properties{
    4.         _MainTex("Base (RGB)", 2D) = "white" {}
    5.         _MaskTex("Mask Texture", 2D) = "white" {}
    6.         _MaskColor("Mask Color", Color) = (1,1,1,1)
    7.         _Color("Base Color", Color) = (1,1,1,1)
    8.     }
    9.         SubShader{
    10.             Tags { "RenderType" = "Opaque" "Queue" = "Transparent" }
    11.             LOD 100
    12.             Blend SrcAlpha OneMinusSrcAlpha
    13.             Pass {
    14.                 CGPROGRAM
    15.                     #pragma vertex vert
    16.                     #pragma fragment frag
    17.                     #pragma target 2.0
    18.                     #include "UnityCG.cginc"
    19.                     struct appdata_t {
    20.                         float4 vertex : POSITION;
    21.                         float2 texcoord : TEXCOORD0;
    22.                         UNITY_VERTEX_INPUT_INSTANCE_ID
    23.                     };
    24.                     struct v2f {
    25.                         float4 vertex : SV_POSITION;
    26.                         float2 texcoord : TEXCOORD0;
    27.                         UNITY_VERTEX_OUTPUT_STEREO
    28.                     };
    29.          
    30.                     sampler2D _MainTex;
    31.                     sampler2D _MaskTex;
    32.                     float4 _MainTex_ST;
    33.                     float4 _Color;
    34.                     float4 _MaskColor;
    35.                     v2f vert(appdata_t v)
    36.                     {
    37.                         v2f o;
    38.                         UNITY_SETUP_INSTANCE_ID(v);
    39.                         UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    40.                         o.vertex = UnityObjectToClipPos(v.vertex);
    41.                         o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    42.                         UNITY_TRANSFER_FOG(o,o.vertex);
    43.                         return o;
    44.                     }
    45.                     fixed4 frag(v2f i) : SV_Target
    46.                     {
    47.                         fixed4 col = tex2D(_MainTex, i.texcoord);
    48.                         col *= _Color;
    49.                         UNITY_APPLY_FOG(i.fogCoord, col);
    50.                         // UNITY_OPAQUE_ALPHA(col.a);
    51.                         return col;
    52.                     }
    53.                 ENDCG
    54.             }
    55.         }
    56. }
    57.  
    I have _MaskTex("Mask Texture", 2D) = "white" {} and _MaskColor("Mask Color", Color) = (1,1,1,1) for the mask texture itself and for the color I want to use as mask (be it green, red, or whatever, I have it so I can change it dinamically). It supports transparency as I wanted it.

    Then I know I have to apply this to check if the pixel is affected by the mask or not:

    float isMask = tex2D(_MaskTex, input.texCoord.xy) == _MaskColor;

    I guess I have to put it inside the Frag function? Because it something at applies to every pixel, not vertex.

    Then I would need to multiply it by a color from the _Color property. Again, I think this should go inside the frag function?

    The Fragment function looks like this:


    Code (CSharp):
    1.                     fixed4 frag(v2f i) : SV_Target
    2.                     {
    3.                         fixed4 col = tex2D(_MainTex, i.texcoord);
    4.                
    5.                         float isMask = tex2D(_MaskTex, i.texcoord) == _MaskColor;
    6.                
    7.                         col = ((1 - isMask)*col) + (isMask * _Color);
    8.  
    9.                         UNITY_APPLY_FOG(i.fogCoord, col);
    10.                         // UNITY_OPAQUE_ALPHA(col.a);
    11.                         return col;
    12.                     }
    It recognizes the Mask, but then applies de color to the wrong part as seen in this GIF:

    https://gyazo.com/5ae99f03b5da5ff25cb466bfbf5815a6

    EDIT:

    Finally I got it working!! Here's the Fragment function:

    Code (CSharp):
    1. fixed4 frag(v2f i) : SV_Target
    2.                     {
    3.                         fixed4 col = tex2D(_MainTex, i.texcoord);
    4.                    
    5.                         float isMask = tex2D(_MaskTex, i.texcoord) != _MaskColor;
    6.                    
    7.                         col = ((1 - isMask)*col) + (isMask * (col*_Color));
    8.  
    9.                         UNITY_APPLY_FOG(i.fogCoord, col);
    10.                         // UNITY_OPAQUE_ALPHA(col.a);
    11.                         return col;
    12.                     }
    The problem is the change between one color and the other is too sharp and looks ugly:

    https://gyazo.com/6e67a6849ca1b7e57b90b87dd7ffffb6

    Is there a way to apply some kind of Antialiasing or lerp to the borders? I have no idea how to approach that.

    Also, with this changes, I lost de hability to make the whole unit semi-transparent, as the Alpha is also using the mask: https://gyazo.com/67d99e36372dc1ea1d776f996af811e6

    How could I isolate the color channels for the mask and leave the Alpha channel to affect everything ?
     
    Last edited: Apr 6, 2020
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Using a boolean mask (a
    ==
    or
    !=
    ) is going to be a hard edge almost no matter what you do. The better solution is to just use a single mask channel as is. For example:
    float mask = tex2D(_MaskTex, i.texcoord).r;

    Which would use the red channel as the mask. Obviously that doesn’t let you configure the color, but as long as you’re okay with only using one channel you can use shader variants to switch, or a dot product.
    float mask = dot(tex2D(_MaskTex, i.texcoord), _MaskChannel);

    As long as the
    _MaskChannel
    is always a vector value with one component a 1 and the rest 0s, like Vector4(1f, 0f, 0f, 0f) for the red channel, you can choose the channel to use.

    Random note, this is a lerp.
    col = lerp(col, col * _Color, isMask);


    Then you want to set the alpha of the final color value before returning it.
     
  6. MaRTiDoRe

    MaRTiDoRe

    Joined:
    Jul 4, 2011
    Posts:
    41
    Hey bgolus,

    Very useful information!

    Using the whole channel and not a single key color was what I really needed. That way I can apply a gaussian filter to the mask to smooth the borders.

    Forcing the Alpha value before returning it worked like a charm too.

    Now I have the exact shader I was looking for. Here is the result:

    https://gyazo.com/fa6d7b96fad96095c3fa9af69be6215c

    I can get as many colours as I want with just one texture, before I had to have one texture (atlas) for each different color. It was really unefficient memory wise.

    Thanks a lot for sharing your expertise bgolus, really apprecianted!

    Gonna return the favor to the community and leave the whole shader here just in case someone needs it in the future:

    The shader uses the Green channel for the mask

    Code (CSharp):
    1. // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
    2. // Unlit shader. Simplest possible textured shader.
    3. // - no lighting
    4. // - no lightmap support
    5. // - no per-material color
    6. // THIS SHADER USES THE GREEN CHANNEL FOR THE MASK
    7. Shader "Unlit/TransparentTextureMask" {
    8.     Properties{
    9.         _MainTex("Base (RGB)", 2D) = "white" {}
    10.         _MaskTex("Mask Texture", 2D) = "white" {}
    11.         _Color("Base Color", Color) = (1,1,1,1)
    12.     }
    13.         SubShader{
    14.             Tags { "RenderType" = "Opaque" "Queue" = "Transparent" }
    15.             LOD 100
    16.             Blend SrcAlpha OneMinusSrcAlpha
    17.             Pass {
    18.                 CGPROGRAM
    19.                     #pragma vertex vert
    20.                     #pragma fragment frag
    21.                     #pragma target 2.0
    22.                     #include "UnityCG.cginc"
    23.                     struct appdata_t {
    24.                         float4 vertex : POSITION;
    25.                         float2 texcoord : TEXCOORD0;
    26.                         UNITY_VERTEX_INPUT_INSTANCE_ID
    27.                     };
    28.                     struct v2f {
    29.                         float4 vertex : SV_POSITION;
    30.                         float2 texcoord : TEXCOORD0;
    31.                         UNITY_VERTEX_OUTPUT_STEREO
    32.                     };
    33.                     sampler2D _MainTex;
    34.                     sampler2D _MaskTex;
    35.                     float4 _MainTex_ST;
    36.                     float4 _Color;
    37.                     float4 _MaskColor;
    38.                     v2f vert(appdata_t v)
    39.                     {
    40.                         v2f o;
    41.                         UNITY_SETUP_INSTANCE_ID(v);
    42.                         UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    43.                         o.vertex = UnityObjectToClipPos(v.vertex);
    44.                         o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    45.                         UNITY_TRANSFER_FOG(o,o.vertex);
    46.                         return o;
    47.                     }
    48.                     fixed4 frag(v2f i) : SV_Target
    49.                     {
    50.                         fixed4 col = tex2D(_MainTex, i.texcoord);
    51.                  
    52.                         float mask = tex2D(_MaskTex, i.texcoord).g; //green channel
    53.                  
    54.                         col = ((1 - mask)*col) + (mask * (col*_Color));
    55.  
    56.                         col.a = _Color.a;
    57.                         UNITY_APPLY_FOG(i.fogCoord, col);
    58.                         // UNITY_OPAQUE_ALPHA(col.a);
    59.                         return col;
    60.                     }
    61.                 ENDCG
    62.             }
    63.         }
    64. }
    The next thing I'd need would be a Dissolve effect, but that's a bit too complex... I'm happy with the result!

    The game I'm developing is this one btw



    It would be cool if the units dissolved (https://gyazo.com/788e1dbfd16e69939652174a89e0bb3d) when reaching the enemy base, instead of disappearing instantly.
     
    Last edited: Apr 7, 2020
    bgolus likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Dissolve effects are popular for tutorials, in large part because they're super simple to implement, but have a big visual impact.

    For example, this line of shader code is all that's needed (apart from defining the properties and uniforms) to do a basic dissolve.
    Code (csharp):
    1. clip(tex2D(_DissolveAlphaTex, i.uv).a - _Dissolve);
    If you want a burning glow or similar, it's another line or two on top of that and you're done.

    Lots of people who do shader tutorials that focus on node based systems (like Shader Graph) will often have this as one of the first tutorials they show because it's one of the few effects you can accomplish in such few nodes that they can all be in view at one time. ;)
     
  8. MaRTiDoRe

    MaRTiDoRe

    Joined:
    Jul 4, 2011
    Posts:
    41
    I was a bit reluctant about expanding the shader anymore given my inexperience, but your words encouraged me to try it.

    There are many tutorials but most of them are Shader Graph based, and I want to do it through code. Anyways I already found an interesting tutorial.

    I'll try to add the dissolve effect and see whats happens!

    I'll post the result here