Notifications
Article
Breaking Down the Layers of a Transition Shader
Published a year ago
167
0
Shaders are blood magic, it's true, but here's a page out of our shader-by-numbers spell book
Let’s chat shaders
Here’s the process I went through to create a shader to cure / de infect the environment. To simulate the infection pealing off trees, plants, etc. 
Why make a shader? Frame by frame animation would be tedious and sprite specific. 
The basic theory was to have a the base sprite and an infection texture that’s blended together based on a noise texture mask and a threshold. Here’s where I started. Replace the main texture pixel with secondary texture based on the mask and threshold.
 
// just replace color based on mask
fixed4 frag (v2f i) : SV_Target
{
fixed4 main = tex2D(_MainTex, i.texcoord); // background texture
fixed4 mask = tex2D(_TransitionMask, i.texcoord); // masking texture
fixed4 sec = tex2D(_SecondaryTex, i.texcoord); // foreground texture
 if((mask.r + mask.g + mask.b) * 0.33333 < _Threshold)
{
      sec.rgb *= sec.a;
      return sec;
}
    else
    {
     main *= i.color;
     main.rgb *= main.a;
     return main;
    }
}
 
How the masking with the noise texture works.
  • Add the rgb values which would give us a number between 0 and 3
  • Multiply by 0.33333 giving a number between 0 - 1, to get the whiteness of the pixel
  • 0 - black and 1 white
  • the threshold is between 0 - 1
  • thus if the pixel in the mask texture less than the threshold. the background (main) gets replaced with the foreground (infection) texture
 
but if the foreground texture has holes, you get the holes as well.
 
Now, I’d like to show the back texture through those holes. So we need to evaluate the alpha channel against some cutoff value and voila we skip the pixels that have an alpha less than the cutoff value.
// check if the sec.a is greater than a cutoff
if((mask.r + mask.g + mask.b) * 0.33333 < _Threshold && sec.a > 0.01)
{
  sec.rgb *= sec.a;
  return sec;
}
 
 
hold up, let’s take a closer look
 
 
because of the antialiasing on the secondary texturing. There’s weird artefacts around the edges that become transparent. So, I’ll add a slider for the cutoff value to find just the right 
 
 
// check if the sec.a is greater than a cutoff
if((mask.r + mask.g + mask.b) * 0.33333 < _Threshold && sec.a > _Alphacutoff)
{
     sec.rgb *= sec.a;
     return sec;
}
 
That kinda works and the the minute changes in alpha are taken care of but now you’ve got really hard edges. I decided to blend the two textures instead of picking one or the other. Hence, I don’t need some cutoff value.
 
// blend the two textures instead of replace one with the other
fixed4 frag (v2f i) : SV_Target
{
fixed4 main = tex2D(_MainTex, i.texcoord); // background texture
fixed4 mask = tex2D(_TransitionMask, i.texcoord); // masking texture
fixed4 sec = tex2D(_SecondaryTex, i.texcoord); // foreground texture
 main.rgb *= main.a;
main *= i.color;
 if((mask.r + mask.g + mask.b) * 0.33333 < _Threshold)
{
     // blend main and sec
  fixed4 col = fixed4(0, 0, 0, 0);
  col.a = 1 - (1 - sec.a) * (1 - main.a);
  if (col.a < 0.00001)
   return col;
  col.r = sec.r * sec.a / col.a + main.r * main.a * (1 - sec.a) / col.a;
  col.g = sec.g * sec.a / col.a + main.g * main.a * (1 - sec.a) / col.a;
  col.b = sec.b * sec.a / col.a + main.b * main.a * (1 - sec.a) / col.a;
  return col;
}
    else
    {
     return main;
    }
}
 
 
Good to go, right. Nope. I thought so too. Then I threw in some of the actual sprites that we’re using for the game and then this happened. :O 
 
 
Turns out there’s more to the sprite that meets the eye. It’s about how the SpriteRenderer in Unity creates the mesh. 
 
 
To stop secondary texture going over the obviously transparent part of the sprite. I used the same technique as before and just had a alpha check on the main texture before I do the blending of the two textures. Voila!
 
// added alpha cutoff to remove "transparent" shit
fixed4 frag (v2f i) : SV_Target
{
fixed4 main = tex2D(_MainTex, i.texcoord); // background texture
fixed4 mask = tex2D(_TransitionMask, i.texcoord); // masking texture
fixed4 sec = tex2D(_SecondaryTex, i.texcoord); // foreground texture
 main.rgb *= main.a;
main *= i.color;
 if((mask.r + mask.g + mask.b) * 0.33333 < _Threshold && main.a > _AlphaCutoff)
{
     // blend main and sec
  fixed4 col = fixed4(0, 0, 0, 0);
  col.a = 1 - (1 - sec.a) * (1 - main.a);
  if (col.a < 0.00001)
   return col;
  col.r = sec.r * sec.a / col.a + main.r * main.a * (1 - sec.a) / col.a;
  col.g = sec.g * sec.a / col.a + main.g * main.a * (1 - sec.a) / col.a;
  col.b = sec.b * sec.a / col.a + main.b * main.a * (1 - sec.a) / col.a;
  return col;
}
    else
    {
     return main;
    }
}
 
The shader can be used with different textures and masks to give a different feeling. Try it for yourself, here's the code. 
Throw me any questions or suggestions. We all learning here. :)
your boy, sugar
BM
Ben Myres
2
Comments