Search code examples
unity-game-engineshadervertex-shader

Shader that assigns a different color to different sections of an object in Unity


I have a set of data that has different segments of color e.g.

  • item1: [red: 3 units, blue: 5 units, green: 4 units, white: 5 units] (total units 17)

  • item2: [red: 4 units, green: 2 units, white: 2 units] (total units 8)

  • itemN: ...

I've created objects in Unity that have a size relative to the total number of units e.g.

  • item1: ----------------- (17)
  • item2: -------- (8)

I would like to color all the objects based on the distribution they have e.g.

  • item1: rrr|bbbbb|gggg|wwwww
  • item2: rrrr|gg|ww

Goal

What I hope to do is instantiate one item with a shader on it that takes the number of units of each color and colors segments of it appropriately.

What I've done so far

I've created m smaller segment-objects, where m is the number of segments per item. I then color these segment-objects appropriately and place them next to each other so they look like one multi-colored item.

Why I think this is bad

This is a working solution, but there can be thousands of items with thousands of segments and I believe it would vastly increase performance to cut this step out.

I don't have any experience with shaders, but this feels like it should be an already solved problem and I don't want to reinvent the wheel. That said, if an exact solution to my problem doesn't exist, shader code that achieves some similar functionality would be also be perfect.

Example photo

stacked bar graph

Update: Current Shader Code And Explanation

I found a shader online that colored a bar, splitting it in two -> Health and Damages. I then modified that code to have three sections, Health, Damages and Shield to get me closer to my stacked bar type ideal. I also have a C# script that calls

GetComponent<MeshRenderer>().material.SetFloat("_LifePercent", percent);
GetComponent<MeshRenderer>().material.SetFloat("_DamagesPercent", percent);
GetComponent<MeshRenderer>().material.SetFloat("_ShieldPercent", percent);

Shader

  Shader "Sprites/HealthBar"
    {
      Properties
      {
       [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}

       [Header(Life)]_LifeColor ("Main Color", Color) = (0.2,1,0.2,1)
      _LifePercent ("Life Percent", Float) = 1

      [Header(Damages)]_DamagesColor ("Damages color", Color) = (1,1,0,1)
      _DamagesPercent ("Damages Percent", Float) = 0

      [Header(Shield)]_ShieldColor ("Shield color", Color) = (.2, .2, 1, 0)
      _ShieldPercent ("Shield Percent", Float) = 0

  }

  SubShader
  {
      Tags
      { 
          "Queue"="Transparent" 
          "IgnoreProjector"="True" 
          "RenderType"="Transparent" 
          "PreviewType"="Plane"
          "CanUseSpriteAtlas"="True"
      }

      Cull Off
      Lighting Off
      ZWrite Off
      Blend One OneMinusSrcAlpha

      Pass
      {
      CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile _ PIXELSNAP_ON
          #include "UnityCG.cginc"

          struct appdata_t
          {
              float4 vertex   : POSITION;
              float4 color    : COLOR;
              float2 texcoord : TEXCOORD0;
          };

          struct v2f
          {
              float4 vertex   : SV_POSITION;
              fixed4 color    : COLOR;
              half2 texcoord  : TEXCOORD0;
          };

          fixed4 _LifeColor;
          half _LifePercent;

          fixed4 _DamagesColor;
          half _DamagesPercent;

          fixed4 _ShieldColor;
          half _ShieldPercent;

          v2f vert(appdata_t IN)
          {
              v2f OUT;
              OUT.vertex = UnityObjectToClipPos(IN.vertex);
              OUT.texcoord = IN.texcoord;

              return OUT;
          }

          sampler2D _MainTex;

          fixed4 frag(v2f IN) : SV_Target
          {
              fixed4 c = tex2D(_MainTex, IN.texcoord);

              if ( IN.texcoord.x > _LifePercent + _DamagesPercent + _ShieldPercent )
              {
                c.a = 0;
              }
              else if ( IN.texcoord.x < _LifePercent )
              {
                    c *= _LifeColor;
              }

              else if ( IN.texcoord.x < _LifePercent + _ShieldPercent && IN.texcoord.x > _LifePercent )
              {
                    c *= _ShieldColor;
              }
              // if we weren't in the previous two segments we're now in the damages segment
              else
              {
                c *= _DamagesColor;
              }
              c.rgb *= c.a;
              return c;
          }
      ENDCG
      }
  }

}

Here's the source of the shader before I modified it: unity life bar shader

The object with the material / shader applied just seems to have the mainTex, with no color anywhere. I also don't fully understand the operations I'm performing on the fixed4 "c". What am I missing?


Solution

  • I spent some more time reading tutorials, specifically from the checkerboard tutorial from unity: unity vertex/fragment examples

    stacked shader 33-33-34

    stacked shader 20-40-40

    This is what I've got now, it's working pretty much how I expect it to:

    Shader "Sprites/HealthBar"
    {
      Properties
      {
         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
          [Header(Life)]_LifeColor ("Main Color", Color) = (0.2,1,0.2,1)
          _LifePercent ("Life Percent", Float) = 1
    
          [Header(Damages)]_DamagesColor ("Damages color", Color) = (1,1,0,1)
          _DamagesPercent ("Damages Percent", Float) = 0
    
          [Header(Shield)]_ShieldColor ("Shield color", Color) = (.2, .2, 1, 0)
          _ShieldPercent ("Shield Percent", Float) = 0
    
      }
    
      SubShader
      {
          Pass
          {
          CGPROGRAM
              #pragma vertex vert
              #pragma fragment frag
              #include "UnityCG.cginc"
    
              struct v2f
              {
                  float4 vertex   : SV_POSITION;
                  float2 uv        : TEXCOORD0;
              };
    
              fixed4 _LifeColor;
              half _LifePercent;
    
              fixed4 _DamagesColor;
              half _DamagesPercent;
    
              fixed4 _ShieldColor;
              half _ShieldPercent;
    
              v2f vert(float4 pos : POSITION, float2 uv : TEXCOORD0)
              {
                v2f o;
                o.vertex = UnityObjectToClipPos(pos);
                o.uv = uv * 100;
                return o;
              }
    
              fixed4 frag(v2f IN) : SV_Target
              {
                  float2 c = IN.uv;
                  fixed4 cout;
    
                  if (c.y < _LifePercent){
                    cout = _LifeColor;
                  }
                  else if (c.y > _LifePercent && c.y < _DamagesPercent + _LifePercent){
                    cout = _DamagesColor;
                  }
                  else {
                    cout = _ShieldColor;
                  }
                  return cout;
    
              }
          ENDCG
          }
      }
    

    }