Search Unity

Health Bar "Reveal" shader with 1 drawcall, 1 texture

Discussion in 'Shaders' started by n0mad, Mar 2, 2012.

  1. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Hello,

    After playing with surface shaders, I found a way to make a reveal type shader for Health bars in any UI.

    The way it works is simple : It multiplies the image alpha with a static mask value for the whole texture, hiding it progressively from left to right and from bottom to top, all controlled with a slider.

    The slider range is UV values, from 0 to 1, so all you have to do is at runtime to specify the said sprite UVs as this value (or it would be unprecise, as 1 = whole texture is masked, 0 = none).
    The shader also take into account the vertex color.

    The advantage of this technique over AlphaTesting a Mask texture is obviously performance for iOS / PowerVR hardware. As mentionned numerous times in this forum and in Unity docs, AlphaTest for iOS is to be avoided at all costs (some old thread).

    Code (csharp):
    1. Shader "MaskedSprite" {
    2. Properties {
    3.     _MainTex ("Texture", 2D) = "white" {}
    4.     _CutoffX("CutoffX", Range(0.0,1.0)) = 1
    5.     _CutoffY("CutoffY", Range(0.0,1.0)) = 1
    6. }
    7.  
    8.  
    9.    
    10.     Category {
    11.    
    12.    
    13.        
    14.        
    15.    
    16.         Cull off
    17.         Blend SrcAlpha OneMinusSrcAlpha
    18.         Lighting Off
    19.  
    20.            
    21.  
    22. Fog { Mode Off }
    23.  
    24.    
    25.      
    26.     SubShader {
    27.        
    28.         Tags {"Queue"="Transparent" "RenderType"="Transparent"}
    29.        
    30.  
    31.     CGPROGRAM
    32.       #pragma surface surf Unlit alpha
    33.  
    34.      half4 LightingUnlit (SurfaceOutput s, half3 lightDir, half atten) {
    35.  
    36.           half4 c;
    37.           c.rgb = s.Albedo;
    38.           c.a = s.Alpha;
    39.           return c;
    40.       }
    41.      
    42.         sampler2D _MainTex;
    43.        
    44.        struct Input {
    45.         float2 uv_MainTex;
    46.          float4 color : Color;
    47.          };
    48.      
    49.     fixed _CutoffX;
    50.     fixed _CutoffY;
    51.  
    52.       void surf (Input IN, inout SurfaceOutput o) {
    53.      
    54.     half4 tex = tex2D (_MainTex, IN.uv_MainTex);
    55.        
    56.     o.Albedo = IN.color.rgb*tex.rgb;
    57.         o.Alpha = IN.uv_MainTex.x > _CutoffX ? 0 : IN.uv_MainTex.y > _CutoffY ? 0 : IN.color.a*tex.a;
    58.        
    59.       }
    60.  
    61.  
    62.      
    63.       ENDCG
    64.        
    65.        
    66. }
    67.  
    68. }
    69.  
    70. }
    I bolded the important part (so you could copy paste it in your own shader).
    The mask is based on a rectangular hiding, indeed.

    Example : if I want to mask my health bar from right to left, I would have to put it this shader, and at runtime perform (pseudo code to gain time, but you get the idea) :

    HealthBar.material.SetFloat("_CutoffX", HealthBar_Mesh_UV_TopRight-myValue);

    If you want the hide to operate on the left side instead of right, you juste invert the comparison signs in the o.Alpha operation.

    Hope it helps.
     
    Last edited: Mar 2, 2012
  2. HashbangGames

    HashbangGames

    Joined:
    May 7, 2011
    Posts:
    40
    Awesome Work! After spending hours and hours of research, this was exactly what i was looking for!
     
  3. db82

    db82

    Joined:
    Mar 14, 2012
    Posts:
    24
    This is a great shader thanks!

    However I'm trying to get it to work on a plane which has a tiled texture. Is there a way to apply this shader across the whole tiled plane instead of the first texture tile only?
     
  4. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Sorry for the (very) late reply (I didn't sub to the thread, my bad) ... I guess you figured it out now, but the answer I'm guessing is no, as it's working on full object, so if you're tiling several objects, it will apply the shader once per object. Maybe I'm wrong though, as I didn't try it.

    Anyway, this shader was made back when there were no GUI solution proposing such trick, but now there are plenty. I suggest that for health bar, you use builtin mask management from popular GUI solutions (ex2D, 2D Toolkit, NGUI, etc). Putting the work on a shader will give an extra drawcall for each object, which is not optimal at all :)
     
  5. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    This is still an extra draw call, but a relatively simple shader. It uses the main texture's alpha channel as a ramp for revealing the RGB. You can use a simple linear gradient for a linear reveal, or put some noise in it for a dissolve effect. Of course, you can also use whatever shape you want for the reveal (radial, conical, etc.) and it should look pretty nice. The lerp avoids alpha testing and conditionals, and also results in a smooth transition rather than a hard edge.

    Code (csharp):
    1. Shader "Custom/Reveal" {
    2.     Properties {
    3.         _MainTex ("Base (RGB) Reveal (A)", 2D) = "white" {}
    4.         _Cutoff ("Cutoff", Range(-0.5, 0.55)) = 0
    5.     }
    6.     SubShader {
    7.         Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    8.         LOD 100
    9.         Alphatest Greater 0
    10.         ZWrite Off
    11.         Blend SrcAlpha OneMinusSrcAlpha
    12.         ColorMask RGB
    13.        
    14.         Pass {
    15.            
    16.             CGPROGRAM
    17.                 #pragma vertex vert
    18.                 #pragma fragment frag
    19.                 #include "UnityCG.cginc"
    20.                
    21.                 struct v2f {
    22.                     float4 pos : SV_POSITION;
    23.                     float2 uv_MainTex : TEXCOORD0;
    24.                 };
    25.                
    26.                 float4 _MainTex_ST;
    27.                
    28.                 v2f vert(appdata_base v) {
    29.                     v2f o;
    30.                     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    31.                     o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
    32.                     return o;
    33.                 }
    34.                
    35.                 sampler2D _MainTex;
    36.                 fixed _Cutoff;
    37.                
    38.                 float4 frag(v2f IN) : COLOR {
    39.                     half4 c = tex2D (_MainTex, IN.uv_MainTex);
    40.                     c.a = lerp(-20, 20, c.a + _Cutoff);
    41.                     return c;
    42.                 }
    43.             ENDCG
    44.         }
    45.     }
    46. }
     

    Attached Files:

    Last edited: Aug 15, 2012
  6. zagahlo

    zagahlo

    Joined:
    Aug 5, 2012
    Posts:
    6
    I'm quite lost about "runtime to specify the said sprite UVs as this value" :/
     
  7. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    LOL, sorry for that, after reading again that part I realize it's awfully written xD
    May I suggest you to use Daniel's solution, which is far more elegant :)
     
  8. idurvesh

    idurvesh

    Joined:
    Jun 9, 2014
    Posts:
    495
    how to reveal part of img with click of mouse?