Different Blending Modes like Add/ Screen/ Overlay + changing Hue/ Tint

Hello everyone,

I’m currently making my first Unity game for my Game Design class and thus learning the ropes with JavaScript, the engine and now ShaderLab.
I’m trying to get multiple textures on one material to be layered on top of each other, some with different blending modes. I found some GLSL and HLSL documentation (included as an attachment) on this, but I’m not sure how I can implement it in my Shader. I’ve set up the Base Color and Texture slots, as well as a Hue slider. My two main questions are as follows:

  1. How can I set a Texture’s blending mode to Overlay/ Add? I understand the SetTexture and Combine principle, but I don’t know how to apply it for these specific Blending Modes.

  2. How can I control a Texture’s Hue/ Tint?

Also, am I correct in assuming these values can be changed real-time by means of a GUI or an in-game event using the getComponent() function?

Thanks in advance,
Patrick

399920–13735–$PhotoshopMathFP.zip (5.58 KB)

I hope this helps

Shader "ChannelBlend/DiffuseAdd" {
Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BlendTex ("Texture", 2D) = "white" {}
    }

    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex;

      };
      sampler2D _MainTex;
          sampler2D _BlendTex;

      void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        half3 blend = tex2D (_BlendTex, IN.uv_MainTex).rgb;
               
                //I think this can be optimized a bit
                o.Albedo.r = min(1.0, o.Albedo.r + blend.r);
                o.Albedo.g = min(1.0, o.Albedo.g + blend.g);
                o.Albedo.b = min(1.0, o.Albedo.b + blend.b);
      }
      ENDCG
    }
    Fallback "Diffuse"
  }

Most photoshop blending styles cannot be expressed on the GPU itself. The three most common hardware supported blending styles are ‘blend’ (‘normal’ in photoshop), ‘additive’ (linear dodge/add) and ‘multiply’ (multiply). You will find several shaders in the particle section of the built-in shaders that implement these. Other popular blending styles, like photoshop’s screen, simply cannot be expressed on the GPU. You will have to try and avoid using such effects when designing for GPU rendered games.

Affecting a texture’s hue or tint can be done in hardware (by writing custom shaders), although hue will require quite an extensive shader (GPU’s only support RGB, so the shader will need to implement the RGB to HSL conversion). For the Battlebot game we made, the customizer does the hue conversion of the textures in script, rather than the shader. I believe there are scripts on the Unify wiki for HSL/RGB conversion to help you with that, but it will be some programming work.

Edit: After checking your attachment, which contains photoshop implementations for blend effects, I feel I need to clarify: you can use any photoshop blending type for colors within a single shader on a single object. However, as far as I know, you just cannot use them between different objects, as you cannot use shader programs to control the blending with the framebuffer. Sadly, using these effects between different objects is by far the most useful application.

I hope this also helps.

Shader "HSB_HSV_Colorpicker_cutdown" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Float) = 0
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      #pragma target 3.0        

        float3 hsv_to_rgb(float3 HSV)
        {
                float3 RGB = HSV.z;
           
                   float var_h = HSV.x * 6;
                   float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
                   float var_1 = HSV.z * (1.0 - HSV.y);
                   float var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
                   float var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
                   if      (var_i == 0) { RGB = float3(HSV.z, var_3, var_1); }
                   else if (var_i == 1) { RGB = float3(var_2, HSV.z, var_1); }
                   else if (var_i == 2) { RGB = float3(var_1, HSV.z, var_3); }
                   else if (var_i == 3) { RGB = float3(var_1, var_2, HSV.z); }
                   else if (var_i == 4) { RGB = float3(var_3, var_1, HSV.z); }
                   else                 { RGB = float3(HSV.z, var_1, var_2); }
           
           return (RGB);
        }

      struct Input {
          float2 uv_MainTex;
      };
      
      float _HueShift;
      
      void surf (Input IN, inout SurfaceOutput o) 
      {
          float3 hsv = float3(_HueShift, IN.uv_MainTex.x, IN.uv_MainTex.y);
          if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
          o.Albedo = half3(hsv_to_rgb(hsv));
      }
      
      ENDCG
    }
    Fallback "Diffuse"
  }

And here’s a version that does two way conversion and modifies _MainTex (the previous one just generates something that looks like photoshop hsb colorpicker).

Shader "HSB_HSV_Colorpicker" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _HueShift("HueShift", Float) = 0
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      #pragma target 3.0
         
        float3 rgb_to_hsv_no_clip(float3 RGB)
        {
                float3 HSV;
           
         float minChannel, maxChannel;
         if (RGB.x > RGB.y) {
          maxChannel = RGB.x;
          minChannel = RGB.y;
         }
         else {
          maxChannel = RGB.y;
          minChannel = RGB.x;
         }
         
         if (RGB.z > maxChannel) maxChannel = RGB.z;
         if (RGB.z < minChannel) minChannel = RGB.z;
           
                HSV.xy = 0;
                HSV.z = maxChannel;
                float delta = maxChannel - minChannel;             //Delta RGB value
                if (delta != 0) {                    // If gray, leave H  S at zero
                   HSV.y = delta / HSV.z;
                   float3 delRGB;
                   delRGB = (HSV.zzz - RGB + 3*delta) / (6.0*delta);
                   if      ( RGB.x == HSV.z ) HSV.x = delRGB.z - delRGB.y;
                   else if ( RGB.y == HSV.z ) HSV.x = ( 1.0/3.0) + delRGB.x - delRGB.z;
                   else if ( RGB.z == HSV.z ) HSV.x = ( 2.0/3.0) + delRGB.y - delRGB.x;
                }
                return (HSV);
        }

        float3 hsv_to_rgb(float3 HSV)
        {
                float3 RGB = HSV.z;
           
                   float var_h = HSV.x * 6;
                   float var_i = floor(var_h);   // Or ... var_i = floor( var_h )
                   float var_1 = HSV.z * (1.0 - HSV.y);
                   float var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i));
                   float var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i)));
                   if      (var_i == 0) { RGB = float3(HSV.z, var_3, var_1); }
                   else if (var_i == 1) { RGB = float3(var_2, HSV.z, var_1); }
                   else if (var_i == 2) { RGB = float3(var_1, HSV.z, var_3); }
                   else if (var_i == 3) { RGB = float3(var_1, var_2, HSV.z); }
                   else if (var_i == 4) { RGB = float3(var_3, var_1, HSV.z); }
                   else                 { RGB = float3(HSV.z, var_1, var_2); }
           
           return (RGB);
        }

      struct Input {
          float2 uv_MainTex;
      };
     
      sampler2D _MainTex;
      float _HueShift;
     
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
         
          float3 hsv = rgb_to_hsv_no_clip(o.Albedo.xyz);
          hsv.x+=_HueShift;
                 
          if ( hsv.x > 1.0 ) { hsv.x -= 1.0; }
          o.Albedo = half3(hsv_to_rgb(hsv));

      }
     
      ENDCG
    }
    Fallback "Diffuse"
  }

o.O I’m so sorry, I can’t believe I never noticed these answers! I’ll be sure to give it all a try, thank you both so much!! =D
@Tom: Blending multiple textures on a single object is all I need luckily! =)

I can’t seem to get these to work in the new Unity3d 3.5. I’m not sure what changed in Shaders or how to go about fixing it.

Woah, nostalgia topic!

Anywho, I just did a quick test on two of the shaders and they both worked just fine. Where are yuu experiencing trouble? To use these shaders you have to:

  • Create a new shader (.shader file) and copy the shader code into this.
  • Create a new material and set it to use that newly created shader.

-Patrick

does anyone have a shader for photoshop “screen”?
also known as negative multiply…

the code should be output= 1-((1-a)*(1-b))

so it should be something like OneMinus (OneMinusSourceColor)* (OneMinusDstColor)

I think you might be misunderstanding what shaders do. “Screen” is one type of blending operation. There are an infinite number of possible shaders that use a screen operation somewhere.

If you’re looking for parameters to the Blend command that will produce a screen blend, that’s unfortunately not possible due to the limitations of fixed function blending. To get a screen operation into the final blend, you would need to get the destination colour from a more versatile source, such as a GrabPass. The documentation for GrabPass is here.

Actually you can do Screen using the blending hardware. I did it in my Shader Wizard asset. But other ones like Overlay, Hard Light etc you can only do between two textures within the shader, and not against the backbuffer.

I must be missing something, then. Screen reduces like this:

1 - (1 - a) (1 - b)
1 - 1 + a + b - ab
a + b - ab

The closest I can get is:

BlendOp Sub
DstColor One

Which gives you:

1 * b - b * a
b - ab

…which is of course missing the a term.

GrabPass is not working for me…

thanks for the answers!

sry imaginaryhuman but i dont have the money for your plugins, but they look very nice!

Daniel, can you give me a shadercode example for implementing this?

Perhaps you don’t have Unity Pro? GrabPass is a Pro-only feature.

If Imaginary Human tells us how to accomplish screen blending using blending hardware, then you shouldn’t need Pro.

A was able to reproduce some of the blend modes without GrabPass with the help of Blend and BlendOp commands.

Darken:
BlendOp Min
Blend One One

Lighten:
BlendOp Max
Blend One One

Linear Burn:
BlendOp RevSub
Blend One One

Linear Dodge:
Blend One One

Multiply:
Blend DstColor OneMinusSrcAlpha

All the others are achievable with GrabPass.

btw, I have a package on the store, that allow to easily apply all the blend modes (22, like in Photoshop) to GUI elements: Unity Asset Store - The Best Assets for Game Making

3 Likes

Hmm, let’s see… You were quite close:
a + b - ab
can be also expressed as:
a + (1 - a) * b
which can be translated as a simple addition:
Blend One OneMinusSrcColor
or symmetrically:
Blend OneMinusDstColor One

which is already included in the manual as “Soft Additive” blending… Maybe it needs some clarification that it’s equivalent to Screen blending?

And for simulating the “Color” Photoshop layer effect? I can reward anybody with a working Unity shader, because I am really unable to find this anywhere on Google.

If you specify color blending as just multiplying the destination color with a luminosity normalized source color, then it can be done with just hardware blending as well… Otherwise you will need to have it either as a post processing effect or use grab pass or command buffer magic. PM me the details if you want me to write you one.