Search Unity

(noob) frag shader to combine textures on sprite

Discussion in 'Shaders' started by coshea, Mar 8, 2018.

  1. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    I decided its time for me to learn shaders, reading some tutorials and watching Unite talks. This is what I want to achieve:



    So my base texture acts like a mask (for alpha shape) but also I can tint it a colour. This base texture is a character sprite with more detail (trying to get this working using a basic white shape). I want to overlay a 2nd texture, but be able to tint that one too. That 2nd texture overlay, imagine a simple photoshop layer with the tint and alpha changed.

    So progress:



    So trying this out in the frag shader I do
    fixed4 returnTexture = (mainTexture * _ColorMain) + (overlayTexture * _ColorOverlay);

    Output


    fixed4 returnTexture = (mainTexture * _ColorMain) + (overlayTexture * _ColorOverlay);


    fixed4 returnTexture = mainTexture * _ColorMain;
    if(overlayTexture.a > 0 && mainTexture.a>0){
    returnTexture += (overlayTexture*_ColorOverlay);
    }
    returnTexture.a = mainTexture.a;


    getting closer...


    fixed4 returnTexture = mainTexture * _ColorMain;
    if(overlayTexture.a > 0 && mainTexture.a>0){
    returnTexture *= (overlayTexture*_ColorOverlay);
    }
    returnTexture.a = mainTexture.a;



    Anyone have any tips for me on the way to add a texture (on top of) another? Some sort of blend mode I'm not understanding yet, instead of + and * ?

    Many thanks
     
    Last edited: Mar 8, 2018
  2. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    Oops here is the shader code so far

    Code (csharp):
    1.  
    2. Shader "MyTests/textureCombine"
    3. {
    4.  
    5.     Properties
    6.     {
    7.         // main
    8.         _ColorMain ("Base Tint", Color) = (1,1,1,1)
    9.         _MainTex ("Base Texture", 2D) = "white" {}
    10.  
    11.         // overlay
    12.         _ColorOverlay ("Overlay Tint", Color) = (1,1,1,1)
    13.         _OverlayTex ("Overlay Texture", 2D) = "white" {}
    14.  
    15.     }
    16.  
    17.     SubShader
    18.     {
    19.         Tags {
    20.             "IgnoreProjector"="True"
    21.             "Queue"="Transparent"
    22.             "RenderType"="Transparent"
    23.         }
    24.         ZWrite Off
    25.         Lighting Off
    26.         Cull Off
    27.         Fog { Mode Off }
    28.         Blend SrcAlpha OneMinusSrcAlpha
    29.        
    30.         Pass
    31.         {
    32.             CGPROGRAM
    33.             #pragma vertex vert
    34.             #pragma fragment frag
    35.  
    36.             #pragma fragmentoption ARB_precision_hint_fastest
    37.             #include "UnityCG.cginc"
    38.  
    39.             // variables
    40.             fixed4 _ColorMain;
    41.             fixed4 _ColorOverlay;
    42.  
    43.             sampler2D _MainTex;
    44.             sampler2D _OverlayTex;
    45.  
    46.  
    47.             // input
    48.             struct vertexIn
    49.             {
    50.                 float4 vertex : POSITION;
    51.                 float2 texcoord : TEXCOORD0;
    52.                 float2 texcoord1 : TEXCOORD1;
    53.             };
    54.  
    55.             // output
    56.             struct vertexOut
    57.             {
    58.                 float4 vertex : SV_POSITION;
    59.                 float4 texcoord01 : TEXCOORD0;
    60.                 float4 texcoord02 : TEXCOORD1;
    61.             };
    62.  
    63.             vertexOut vert(vertexIn v)
    64.             {
    65.                 vertexOut OUT;
    66.                 OUT.vertex = UnityObjectToClipPos(v.vertex);
    67.                 OUT.texcoord01.xy = v.texcoord;
    68.                 OUT.texcoord02.xy = v.texcoord1;
    69.                 return OUT;
    70.             }
    71.  
    72.             // frag
    73.             fixed4 frag(vertexOut IN) : SV_Target
    74.             {
    75.                 fixed4 mainTexture = tex2D(_MainTex, IN.texcoord01.xy);
    76.                 fixed4 overlayTexture = tex2D(_OverlayTex, IN.texcoord02.xy);
    77.  
    78.                 fixed4 returnTexture = mainTexture * _ColorMain;
    79.                 if(overlayTexture.a > 0 && mainTexture.a>0){
    80.                     returnTexture *= (overlayTexture*_ColorOverlay);
    81.                 }
    82.                 returnTexture.a = mainTexture.a;
    83.                 return returnTexture;
    84.             }
    85.  
    86.             ENDCG
    87.         }
    88.     }
    89. }
    90.  
    91.  
    92.  
     
  3. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,723
    Disclaimer: I am very tired and writing this from the top of my head, sooooo, your mileage may vary.
    Code (csharp):
    1. fixed4 mainTexture = tex2D(_MainTex, IN.texcoord01.xy);
    2. fixed4 overlayTexture = tex2D(_OverlayTex, IN.texcoord02.xy);
    3. fixed4 returnTexture = mainTexture * _ColorMain;
    4. overlayTexture.rgb *= _ColorOverlay.rgb;
    5. returnTexture.rgb = overlayTexture.a * _ColorOverlay.a * (overlayTexture.rgb-returnTexture.rgb) + returnTexture.rgb;
    6. returnTexture.a = mainTexture.a;
    7. return returnTexture;
     
  4. rsmeenk

    rsmeenk

    Joined:
    Oct 24, 2015
    Posts:
    23
    return fixed4(lerp(mainTexture * _ColorMain, overlayTexture * _ColorOverlay, overlayTexture.a * _ColorOverlay.a).rgb, mainTexture.a);
     
    Last edited: Mar 8, 2018
  5. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    Thanks! This seems to work, I'm just trying to unpack what you did so that I can understand the code and add a third texture on top.
     
  6. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    thanks for replying. the colours are right and the base mask, but the overlay colour alpha doesn't work in this example.
     
  7. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,723
    If you add * _ColorOverlay.a right after overlayTexture.a, rsmeenk's code will work just fine.

    (because basically it's the same thing with mine, only I kinda wrote what the lerp does without using a lerp, because I'm tired :p )
     
  8. rsmeenk

    rsmeenk

    Joined:
    Oct 24, 2015
    Posts:
    23
    Edited my previous post with mentioned comment. Little bit confusing to communicate on Twitter and the forum at the same time.:p

    Your basemap alpha acts as the final alpha used for blending with the rest of the scene. (defines the shape)
    Your overlay alpha is used as the blendfactor for the overlay color. (color override)
    You can stack up multiple lerps (linear interpolations) to build up the final color.

    Hope that help.
     
  9. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    Thanks both for your help.

    So I wanted to add a third texture and tint, I've tried this (making the code longer so I can understand better)

    Code (csharp):
    1.  
    2.             fixed4 frag(vertexOut IN) : SV_Target
    3.             {
    4.                 // texture references
    5.                 fixed4 mainTexture = tex2D(_MainTex, IN.texcoord01.xy);
    6.                 fixed4 overlayTexture = tex2D(_OverlayTex, IN.texcoord02.xy);
    7.                 fixed4 overlayTextureTwo = tex2D(_OverlayTexTwo, IN.texcoord02.xy);
    8.  
    9.                 fixed4 mainTextureTinted = mainTexture * _ColorMain; // main texture tinted
    10.                 fixed4 overlayTextureTinted = overlayTexture * _ColorOverlay; // overlay texture tinted
    11.                 fixed4 overlayTextureTintedTwo = overlayTextureTwo * _ColorOverlayTwo; // 2nd overlay
    12.  
    13.                 fixed4 firstOverlay = mainTexture; // just for a return texture to store (also keeps the alpha from main)
    14.  
    15.                 // one lerp to add first overlay to main texture
    16.                 firstOverlay.rgb = lerp(mainTextureTinted, overlayTextureTinted, overlayTexture.a * _ColorOverlay.a).rgb;
    17.  
    18.                 // 2nd lerp to add 2nd overlay on top of above
    19.                 fixed4 returnTexture = firstOverlay;
    20.  
    21.                 returnTexture.rgb = lerp(firstOverlay, overlayTextureTintedTwo, overlayTextureTwo.a * _ColorOverlayTwo.a).rgb;
    22.  
    23.                 // set to main texture
    24.                 returnTexture.a = mainTexture.a;
    25.  
    26.                 return returnTexture;
    27.  
    28.             }
    29.  
    30.  
    Output...



    Seems to work as expected.

    Could the code be sped up / cleaner, or ok to leave it as above?

    Should work ok on mobile?

    Is there a limit to the number of textures you can combine like this?

    Thanks!
     
  10. rsmeenk

    rsmeenk

    Joined:
    Oct 24, 2015
    Posts:
    23
    There's always a limit. :) And it depends on the number of texture samplers that the hardware supports.
    Sampling textures is a relatively costly operation so you want to minimize those. In your example you are using full four channel textures (rgb + alpha), but this could be reduced to a grayscale texture (since you really only are using the alpha for blend factor). If you combine four grayscale textures (for 4 different overlays) into a combined four channel texture you reduce the number of texture samples. This way you can reduce your samples by a factor of four, but note that this can only be used if you are using the same texture coordinate (which is the case with your two overlay textures). So you save a texture sample and still have room for two more for free! There is no difference in cost between sampling a four channel vs single channel texture.
     
  11. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    Thanks, I'll look into using grayscale for the two overlays.

    The issue I'm trying to solve right now is uv mapping.

    So the base texture is actually a sprite atlas, in my code I'm using the same uv on the overlays, but they are both 512x512 repeating images.

    Whats the best way to keep my base texture using its own uv co-ords and then the overlays using their own?
     
  12. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    Here is what my code looks like at the moment, trying to combine a base texture (on an atlas (tk2d not unity sprite, so basically just mesh with verts)) and a single repeating overlay

    Code (csharp):
    1.  
    2. Shader "MyTest/twoTextureTest"
    3. {
    4.  
    5.     Properties
    6.     {
    7.         _MainTex ("Base Texture", 2D) = "white" {}
    8.         _OverlayTex ("Overlay Texture", 2D) = "white" {}
    9.  
    10.     }
    11.  
    12.     SubShader
    13.     {
    14.         Tags {
    15.             "IgnoreProjector"="True"
    16.             "Queue"="Transparent"
    17.             "RenderType"="Transparent"
    18.         }
    19.         ZWrite Off
    20.         Lighting Off
    21.         Cull Off
    22.         Fog { Mode Off }
    23.         Blend SrcAlpha OneMinusSrcAlpha
    24.        
    25.         Pass
    26.         {
    27.             CGPROGRAM
    28.             #pragma vertex vert
    29.             #pragma fragment frag
    30.  
    31.             #pragma fragmentoption ARB_precision_hint_fastest
    32.             #include "UnityCG.cginc"
    33.  
    34.             // variables
    35.             sampler2D _MainTex;
    36.             sampler2D _OverlayTex;
    37.             float4 _OverlayTex_ST;
    38.  
    39.             // input
    40.             struct vertexIn
    41.             {
    42.                 float4 vertex : POSITION;
    43.                 float2 uv : TEXCOORD0;
    44.                 float2 uv1 : TEXCOORD1;
    45.             };
    46.  
    47.             // output
    48.             struct vertexOut
    49.             {
    50.                 float4 vertex : SV_POSITION;
    51.                 float4 uv : TEXCOORD0;
    52.                 float4 uv1 : TEXCOORD1;
    53.             };
    54.  
    55.             vertexOut vert(vertexIn v)
    56.             {
    57.                 vertexOut OUT;
    58.                 OUT.vertex = UnityObjectToClipPos(v.vertex);
    59.                 OUT.uv.xy = v.uv;
    60.                 OUT.uv1.xy = TRANSFORM_TEX(v.uv1, _OverlayTex);
    61.                 return OUT;
    62.             }
    63.  
    64.             // frag
    65.             fixed4 frag(vertexOut IN) : SV_Target
    66.             {
    67.                
    68.                 fixed4 mainTexture = tex2D(_MainTex, IN.uv.xy);
    69.                 fixed4 overlayTexture = tex2D(_OverlayTex, IN.uv1.xy);
    70.  
    71.                 fixed4 returnTexture = mainTexture;
    72.  
    73.                 returnTexture.rgb = lerp(mainTexture, overlayTexture, overlayTexture.a).rgb;
    74.  
    75.                 return returnTexture;
    76.  
    77.             }
    78.  
    79.             ENDCG
    80.         }
    81.     }
    82. }
    83.  
    84.  
    85.  
     
  13. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    I made this texture to use as the overlay to debug what is happening (black is transparent)





    This is how it looks using the above shader on 3 sprites of different shape and size...



    I find it really unpredictable to see how the overlay will work, the more sprites in the atlas makes it worse.

    Any thoughts on using the same uv positions from the overlay on each mesh, or how to do it properly?

    Many thanks indeed.
     
  14. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,723
    Not sure what tk2d does with second UV channel, but you should be able to use the 1st uv channel again with different tiling parameters.

    What happens when you change
    Code (csharp):
    1. OUT.uv1.xy = TRANSFORM_TEX(v.uv1, _OverlayTex);
    to
    Code (csharp):
    1. OUT.uv1.xy = TRANSFORM_TEX(v.uv, _OverlayTex);
    (although it's probably not what you want again, but at least this should be predictable)
     
  15. rsmeenk

    rsmeenk

    Joined:
    Oct 24, 2015
    Posts:
    23
    For debugging I often output to color in a fragment shader:
    return fixed4(IN.uv.xy, 0.0, 1.0);
    This will create a red/green output that will tell you where 0 is (black) or 1 (full green or red).

    But, for learning purposes it may be easier to start with
    return fixed4(IN.uv.xxx, 1.0);

    See what happens
    and then

    return fixed4(IN.uv.yyy, 1.0);

    This will output the incoming uv coordinate values as grayscale color.
     
    AcidArrow likes this.
  16. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    output using these sprites:


    this is the output from your suggestions @rsmeenk

    return fixed4(IN.uv.xy, 0.0, 1.0);


    return fixed4(IN.uv.xxx, 1.0);


    return fixed4(IN.uv.yyy, 1.0);


    this is how the atlas looks


    basically i want to keep the UV of the base texture as you see from the atlas, but from the overlay it would repeat on its own co-ordinates, not taking texture position from the base and using it on the overlay, using the overlay own co-ords.
     
  17. Remy_Unity

    Remy_Unity

    Unity Technologies

    Joined:
    Oct 3, 2017
    Posts:
    703
    I made up a simple project with what I think you want, see attached.

    I combined 4 overlay masks in one texture like this :
    overlays.png

    And the shader code is this one :
    Code (CSharp):
    1. Shader "Unlit/SpriteMat"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.  
    7.         [NoScaleOffset] _OverlayTex ("Overlays Texture", 2D) = "White"{} // Don't display the tiling/offset here, we'll add them in the next lines
    8.  
    9.         _OverlayST_0 ("Layer 1 Tiling Offset", Vector) = (1,1,0,0) // Express each tiling/offset as vector4 (tiling X & Y ; offset Z & W)
    10.         _OverlayColor_0 ("Layer 1 Color", Color) = (1,0,0,1) // Overlay Layer color
    11.  
    12.         _OverlayST_1 ("Layer 2 Tiling Offset", Vector) = (1,1,0,0)
    13.         _OverlayColor_1 ("Layer 2 Color", Color) = (0,1,0,1)
    14.  
    15.         _OverlayST_2 ("Layer 3 Tiling Offset", Vector) = (1,1,0,0)
    16.         _OverlayColor_2 ("Layer 3 Color", Color) = (0,0,1,1)
    17.  
    18.         _OverlayST_3 ("Layer 3 Tiling Offset", Vector) = (1,1,0,0)
    19.         _OverlayColor_3 ("Layer 3 Color", Color) = (0,1,1,1)
    20.  
    21.     }
    22.     SubShader
    23.     {
    24.         Tags { "RenderType"="Transparent" }
    25.         LOD 100
    26.  
    27.         Pass
    28.         {
    29.             Blend SrcAlpha OneMinusSrcAlpha
    30.  
    31.             CGPROGRAM
    32.             #pragma vertex vert
    33.             #pragma fragment frag
    34.            
    35.             #include "UnityCG.cginc"
    36.  
    37.             struct appdata // vertex shader input
    38.             {
    39.                 float4 vertex : POSITION;
    40.                 float2 uv : TEXCOORD0;
    41.             };
    42.  
    43.             struct v2f // vertex to fragment data
    44.             {
    45.                 float2 uv : TEXCOORD0;
    46.                 float4 vertex : SV_POSITION;
    47.  
    48.                 float4 uvOverlay01 : TEXCOORD1; // uvs of layer 0 (xy) and 1 (zw) with tiling and offset applied
    49.                 float4 uvOverlay23 : TEXCOORD2; // uvs of layer 2 (xy) and 3 (zw) with tiling and offset applied
    50.             };
    51.  
    52.             sampler2D _MainTex;
    53.             float4 _MainTex_ST;
    54.  
    55.             sampler2D _OverlayTex;
    56.             float4 _OverlayST_0;
    57.             float4 _OverlayColor_0;
    58.             float4 _OverlayST_1;
    59.             float4 _OverlayColor_1;
    60.             float4 _OverlayST_2;
    61.             float4 _OverlayColor_2;
    62.             float4 _OverlayST_3;
    63.             float4 _OverlayColor_3;
    64.            
    65.             v2f vert (appdata v)
    66.             {
    67.                 v2f o;
    68.                 o.vertex = UnityObjectToClipPos(v.vertex);
    69.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    70.  
    71.                 o.uvOverlay01 = float4(
    72.                     v.uv * _OverlayST_0.xy + _OverlayST_0.zw,
    73.                     v.uv * _OverlayST_1.xy + _OverlayST_1.zw
    74.                 );
    75.                 o.uvOverlay23 = float4(
    76.                     v.uv * _OverlayST_2.xy + _OverlayST_2.zw,
    77.                     v.uv * _OverlayST_3.xy + _OverlayST_3.zw
    78.                 );
    79.  
    80.                 return o;
    81.             }
    82.            
    83.             fixed4 frag (v2f i) : SV_Target
    84.             {
    85.                 // sample the main texture, never modify the alpha later on
    86.                 fixed4 col = tex2D(_MainTex, i.uv);
    87.  
    88.                 // Apply each layers:
    89.                 // ---- 0 ----
    90.                 fixed l0 = tex2D(_OverlayTex, i.uvOverlay01.xy).r; // Get the layer opacity from texture channel
    91.                 col.rgb = lerp( col.xyz, _OverlayColor_0.rgb, l0 * _OverlayColor_0.a ); // lerp with the previous color and take into account the layer color opacity
    92.                
    93.                 // ---- 1 ----
    94.                 fixed l1 = tex2D(_OverlayTex, i.uvOverlay01.zw).g;
    95.                 col.rgb = lerp( col.xyz, _OverlayColor_1.rgb, l1 * _OverlayColor_1.a );
    96.                
    97.                 // ---- 2 ----
    98.                 fixed l2 = tex2D(_OverlayTex, i.uvOverlay23.xy).b;
    99.                 col.rgb = lerp( col.xyz, _OverlayColor_2.rgb, l2 * _OverlayColor_2.a );
    100.                
    101.                 // ---- 3 ----
    102.                 fixed l3 = tex2D(_OverlayTex, i.uvOverlay23.zw).a;
    103.                 col.rgb = lerp( col.xyz, _OverlayColor_3.rgb, l3 * _OverlayColor_3.a );
    104.  
    105.                 return col;
    106.             }
    107.             ENDCG
    108.         }
    109.     }
    110. }
    If you have any questions on how I made it, feel free to ask.
     

    Attached Files:

  18. coshea

    coshea

    Joined:
    Dec 20, 2012
    Posts:
    319
    HI @Remy_Unity thank you for taking the time to make this example!

    The approach of making the overlay from one image and using the 4 channels to basically make 4 single overlays is a good one. However ultimately I wanted to use different textures with a tint on them, as one of the textures will have multiple colours in.

    Also changed since I posted above, I found that changing the colours on a per sprite basis using MaterialPropertyBlock increases draw calls, so I just wanted to get the tiling working and then work on passing the colour tints hidden in the uv data, that way I could per sprite tint different colours and not use materialproperty block.

    I can post up an example if you want, which version of Unity are you using? (I'm 2017)

    Thanks!
     
  19. Remy_Unity

    Remy_Unity

    Unity Technologies

    Joined:
    Oct 3, 2017
    Posts:
    703
    I'm on so many Unity version that I can't count them anymore XD.
    If you want to have less batched with material property block, look at how implementing GPPU instances : https://docs.unity3d.com/Manual/GPUInstancing.html
    I'm quite curious on how you pass the tints in the uv data :)