Search Unity

Partial multiply shader (for dummies)

Discussion in 'Shaders' started by LeRan, Nov 20, 2016.

  1. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hello forum,

    I needed to do a simple thing but I was unaware that shaders were so complex to learn... So please forgive me if I come here for help.

    I want a "multiply" shader, to multiply the texture I have by a plain color that's in the material, but one that counts only for a fraction (like, half) of the result. Put it otherwise, if I call the colors Final (the one that I'm trying to display), Original (the one that's in the original texture) and Input (the added color to multiply), I'm looking for :

    Final = 0.5 x Original + 0.5 x (Original multiplied by Input)

    I believe that's the result that Gimp or Photoshop would give when setting a half-transparent layer in "multiply" mode over the original texture layer.

    Or in terms of Unity's shaders, I'm trying to do that (sorry for the heretic redaction but I'm not sure how to put it otherwise) :

    0.5 x (Blend DstColor Zero) + 0.5 x (Blend zero one)

    So far I've this shader that works perfectly for the multiply effect, but I'm unable to weight down that effect as said previously. Someone knows how to do that?

    Code (CSharp):
    1. Shader "Custom/MonNewSurfaceShader"
    2. {
    3. Properties
    4. {
    5.     _Color ("Color", Color) = (1,1,1)
    6. }
    7. SubShader
    8. {
    9.     Tags {Queue=Transparent}
    10.     Blend DstColor Zero
    11.     Pass {
    12.         Color [_Color]
    13.         }
    14. }
    15. }
     
  2. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    All right, against all odds I finally found the answer ; it actually decided to switch to an unlit shader, and write it like this.

    Code (CSharp):
    1. Shader "Unlit/LeNewUnlitShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Couleur ("Couleur", Color) = (1,1,1,1)
    7.         _LaProportion("Proportion de couleur", float) = 0.5
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "RenderType"="Opaque" }
    12.         LOD 100
    13.  
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag  
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 float2 uv : TEXCOORD0;
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float2 uv : TEXCOORD0;
    30.                 UNITY_FOG_COORDS(1)
    31.                 float4 vertex : SV_POSITION;
    32.             };
    33.  
    34.             sampler2D _MainTex;
    35.             float4 _MainTex_ST;
    36.          
    37.             v2f vert (appdata v)
    38.             {
    39.                 v2f o;
    40.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    41.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    42.                 UNITY_TRANSFER_FOG(o,o.vertex);
    43.                 return o;
    44.             }
    45.  
    46.             fixed4 _Couleur;
    47.             float _LaProportion;
    48.  
    49.             fixed4 frag (v2f i) : SV_Target
    50.             {
    51.                 // sample the texture
    52.                 fixed4 col = tex2D(_MainTex, i.uv);
    53.                 return (1-_LaProportion)*col+_LaProportion*col*_Couleur;
    54.             }
    55.             ENDCG
    56.         }
    57.     }
    58. }
    The properties allow to set in the inspector, respectively :
    - the tint of the color to apply to the texture.
    - the proportion of the original texture that will be affected (100% means a simple multiply shader, 0% lets the texture unaffected). That emulates the "transparency" of the multiply shader in Gimp/Photoshop.

    Well, it's probably basic but I let this here in case a complete shader's newbie like me comes across the same problem.
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    This can be replaced with the built in function lerp().

    return lerp(col, _Couleur, _LaProportion);

    The actual compiled shader will be the same as your code, but it's less code to write.
     
  4. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hmm, in fact it's not so good. It does work as intended for the textures, but what I would like is to put that shader on a transparent image on top of everything that I need to alter. I could not find a way to make my "unlit shader" do that ; it seems that it's what surface shaders were designed for.

    But I still can't customize the bloody "Blend DstColor Zero".

    Help?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Are you looking for this?
    https://docs.unity3d.com/Manual/SL-Blend.html

    All of Unity's shader documentation is in the "manual" section rather than "scripting" section. Well, all except for random useful tidbits like MaterialPropertyDrawer listing out some additional shader property tools and the actual enum orders if you want to control stuff like the blend mode from script / material properties.
     
  6. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hmm, maybe, but I'm not sure how to do that.. Basically, I need to Lerp multiply and identity, something like :

    Result = 0.4 x (Blend DtsColor Zero) + 0.6 x (Blend One Zero)

    Can this be done with blend ops? Or something else?
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Blend modes are limited to the options you see. There's no way to modify or extend them directly. It's one of the few vestiges of fixed function shaders (the type of shader you were using in your original post) still left in modern GPUs.

    To replicate what you're trying to do you have a couple of options.

    You can sometimes get around this by careful manipulation of the color, alpha and blend mode, depending on exactly what kind of look you're trying to get. If you're trying to replicate the Photoshop Multiply it's actually really easy as the blend is actually just Blend DstColor Zero, but with return lerp(fixed4(1,1,1,1), _Color, _BlendAmount);. If you're trying to replicate a more complex blend mode like hard light or overlay this won't work.

    If what you want is literally 0.4 x (Blend DtsColor Zero) + 0.6 x (Blend One Zero) then you could probably just do two passes, one using Blend DstColor Zero with the output multiplied by 0.4, and one using Blend One Zero with the output multiplied by 0.6. Several of the more complex Photoshop blend modes can be replicated this way, but two passes means rendering each object twice.

    If you want more complex blends than that you need to not use blend modes at all and instead use a GrabPass to get a copy of screen contents and manually blend in the shader. However grab passes can be expensive, especially on mobile platforms.

    If you are trying to do this on mobile, specifically iOS, there's a specific extension that lets you access the screen contents for free without a grab pass. See "Using Shader framebuffer fetch" in https://docs.unity3d.com/Manual/SL-PlatformDifferences.html
    This is limited to PowerVR GPUs (which iOS uses exclusively), though Nvidia Tegra devices have a similar functionality, as does Nvidia desktop running OpenGL, though I don't know if it's possible to use it on those devices with Unity.


    One last thing. Surface shaders are vertex / fragment shader generators. They don't have any abilities or functionality that can't be done with normal a vertex / fragment shader because as far as the GPU is concerned they are just a vertex / fragment shader. You can select a surface shader and click on "Show generated code" to show the code for the (many!) vertex / fragment shaders the game actually uses.
     
    LeRan likes this.
  8. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Thank you very much for your answer, I understood lots of things ! But since I'm quite a shader newbie, I've still a couple question...
    Could you give me a hand and tell me how to write the code to actually tweak the return? In every test I did yesterday, the Blend line and the CGPROGRAM lines seemed to be mutually exclusive, one cancelling the effect of the other, so I must be missing something here...

    Hmm, that would be perfect and even more versatile... I managed to do that but only for a specified texture on a material, using that shader instead of the default sprite shader. Is there a way to have my previous vertex/fragment shader (post #2 in this thread) work, not on the texture passed as parameter to the material, but on "everything that already displays behind that material" ?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    There are some fundamentals of how shaders work and how the rendering pipeline works I feel like you're missing.

    In a vertex / fragment shader the fragment function returns the color to be used as the blend mode's "SrcColor" (source). The first return in that function is the color used. If you have multiple returns all but the first is ignored, so you need to build the color you want over many steps then return the final color you want.

    The very high level overview relevant to this conversation is the GPU renders an object by running a vertex shader to determine the position and fragment shader to determine the color, then stores that color in a buffer using the blend mode and blend ops. Then the next object is rendered, etc. etc. Each object can only write to that buffer, they cannot read from it* so they have no idea what color they're blending with.

    Generally speaking a shader takes the data that's passed to it and outputs a single color value. Each shader has no knowledge of what's already been rendered, or even what else is currently being rendered.

    A grab pass is the thing that's most likely going to work for what you want to do. That's a pass that doesn't actually render anything, but instead makes Unity copy the current contents of the screen to a texture that shaders can access as an input. This will let you do more complex blend functions by not doing any "Blend" and outputting the final color as you want it. However it won't necessarily be the color that is in the buffer at that pixel when the shader is rendering, just the color it was at some point before the entire object was rendered. This is a subtle difference, but means a single mesh (or single batched mesh) with overlapping faces won't blend with each other when using the grab pass, but instead only blend with and show what existed before the mesh was rendered at all.

    * Except for the case I mentioned with PowerVR & iOS in my previous post.

    I highly recommend you read through this:
    http://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d/

    That tutorial doesn't really touch on any of the particular points in this thread, but might get you better understanding Unity's shader system.
     
    LeRan likes this.
  10. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Wow, thanks a lot, that's the very best tutorial I've read so far. I'll give it a read and I might me able to solve my problem.

    That's absolutely right, sadly. The thing is that after having learnt C# and Blender from scratch I was reluctant to also learn Cg too, but I guess that can't be helped. Anyway, thanks for your insightful directions.

    Just to have your opinion : I need my "partial multiply" shader just for the few stacked semi-transparent world map objects in a 2D game (to actually emulate a morning or evening light without having to carry around several tile maps). Do you think that I have to learnt how to do a grab pass? Or is there an easier-to-do solution for just that purpose? Actually the surface shader with multiply through blending (post #1) is ALMOST perfect for my needs, except that I must tune down a little bit the effect or the new color tone is too much visible.

    edit: nevermind, I inadvertently already learnt how to do a grab pass, that tutorial is so good it makes me dizzy:
    http://www.alanzucconi.com/2015/07/01/vertex-and-fragment-shaders-in-unity3d/
     
    Last edited: Nov 21, 2016
  11. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Thanks bgolus for yours explanations and also for the link to the tutorial, you are a great man! And note to newbies like me : I can't recommand enough that read, it's necessary.

    I finally got it right, here it how it looks like.


    And in case someone who does not want to learn Cg wants to do it also, here is the finished product (alpha transparency not managed, but if you set the "proportion" to zero, it completely cancels the effect so alpha did not seem necessary).

    Code (CSharp):
    1. Shader "Unlit/LeSecondUnlitShader"
    2. {
    3.     Properties
    4.     {
    5.         _Couleur ("Couleur", Color) = (1,1,1,1)
    6.         _LaProportion("Proportion de couleur", float) = 0.5
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque"}
    12.         ZWrite On Lighting Off Cull Off Fog { Mode Off } Blend One Zero
    13.         GrabPass { "_GrabTexture" }
    14.      
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #include "UnityCG.cginc"
    21.             sampler2D _GrabTexture;
    22.             struct vin_vct
    23.             {
    24.                 float4 vertex : POSITION;
    25.             };
    26.             struct v2f_vct
    27.             {
    28.                 float4 vertex : POSITION;
    29.                 float4 uvgrab : TEXCOORD1;
    30.             };
    31.             // Vertex function
    32.             v2f_vct vert (vin_vct v)
    33.             {
    34.                 v2f_vct o;
    35.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    36.                 o.uvgrab = ComputeGrabScreenPos(o.vertex);
    37.                 return o;
    38.             }
    39.  
    40.             fixed4 _Couleur; //faut la redéclarer ici sinon ça marche pas
    41.             float _LaProportion;
    42.  
    43.             // Fragment function
    44.             half4 frag (v2f_vct i) : COLOR
    45.             {
    46.                 fixed4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
    47.                 //return _Couleur*col;
    48.                 return (1-_LaProportion)*col+_LaProportion*col*_Couleur;
    49.             }
    50.             ENDCG
    51.         }
    52.     }
    53. }
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Okay, now that I see exactly what you're trying to do and what you want this for, you probably shouldn't use the grab pass technique. You can do the same recoloring of the textures in the initial shader used to draw your tiles, or you can use a post process image effect like tonemapping. Image effects work very similarly to the shader you have, but is setup to be a lot more efficient in getting the "grab". Doing the grab pass route is seriously overkill.

    What shader are you using for drawing your tiles?
     
  13. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Actually, the world map is a stack of a dozen semitransparent overlapping objects imported from Tiled, which use the built-in shader from Tiled2Unity ; I read it but did not understand it completely, so if needed I can copy-paste it here. There are also on the map an undetermined but possibly large number of sprite objects that use the "Sprites-Default" material.

    I want those light effects to emulate different times of the day : morning and evening with a light source around 3000 K, noon around 6000 K etc. I'm still unsure whether some sprite objects will have their own special lighting directly in the sprite created with Blender (and thus won't need the color correction I described earlier) or if absolutely all objects and maps will need that correction (it's a bit less pretty than dedicated renders but more convenient, see note below) but in all cases, that will be many objects.

    That's why I finally decided to opt for the grab pass to only have one material to manage, overlapping everything that needs color correction ; that seemed convenient but admittedly I have no idea of how CPU-intensive those different solutions are, so maybe that was not the best option.

    Note : as a bonus, a test I made : left, the Blender render in evening light, right, a render in white light plus the multiply shader to emulate evening light. (Less pretty but not very different after all in my opinion.)