Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Having a hard time understanding shaders

Discussion in 'Shaders' started by GixG17, Feb 7, 2017.

  1. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I have limited knowledge of how to write custom shaders and I'm trying to do a "simple" double sided shader that lights both sides evenly.

    I got as far as making two passes to get a solid surface on both sides which both takes lighting information (instead of merely Cull Off).

    Now, my problem is this: it only lights both surfaces based on the front face. So if the light is hitting the backface, both sides are dark instead. I tried flipping the normals on the back face but the moment I add the line "#pragma vertex vert", I lose the "both sides are lit evenly" aspect that I want.

    I'm not sure why it's behaving this way and I was wondering if anyone had a solution to what I believe should be a basic thing to do with shaders... which is to apply light on both sides regardless of which side (front/back face) the light is really on.

    Here's the code:
    Code (csharp):
    1.  
    2. Shader "Project/LitDoubleSided" {
    3. Properties {
    4. _TintColor ("Color",Color)=(0.5,0.5,0.5)
    5. _MainTex ("Texture", 2D) = "white" {}
    6. _Cutoff ("Cutoff", float) = 0.5
    7. _Color ("FallbackColor",Color)=(1,1,1,1)
    8. }
    9. SubShader {
    10. Tags { "RenderType"= "Opaque" }
    11. Cull Back
    12. LOD 200
    13.  
    14. CGPROGRAM
    15. #pragma surface surf Standard fullforwardshadows
    16. #pragma target 3.0
    17.  
    18. sampler2D _MainTex;
    19. fixed3 _TintColor;
    20.  
    21. struct Input {
    22. float2 uv_MainTex;
    23. fixed _Cutoff;
    24. };
    25.  
    26. half _Glossiness;
    27. half _Metallic;
    28. fixed4 _Color;
    29.  
    30. void surf (Input IN, inout SurfaceOutputStandard o) {
    31. fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    32. o.Albedo = c.rgb * (_TintColor*2);
    33. o.Alpha = c.a;
    34. }
    35. ENDCG
    36.  
    37. Cull Front
    38. //LOD 200
    39.  
    40. CGPROGRAM
    41. #pragma surface surf Standard fullforwardshadows
    42.  
    43. #pragma target 3.0
    44. //#pragma vertex vert
    45.  
    46. sampler2D _MainTex;
    47. fixed3 _TintColor;
    48.  
    49. struct Input {
    50. float2 uv_MainTex;
    51. fixed _Cutoff;
    52. };
    53.  
    54. half _Glossiness;
    55. half _Metallic;
    56. fixed4 _Color;
    57.  
    58. void vert (inout appdata_full v) {
    59. v.normal *= -1;
    60. }
    61. void surf (Input IN, inout SurfaceOutputStandard o) {
    62. fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    63. o.Albedo = c.rgb * (_TintColor*2);
    64. o.Alpha = c.a;
    65. }
    66. ENDCG
    67. }
    68.  
    69. FallBack "Diffuse"
    70. }
    71.  
    I tried adding a Lambert
    Code (csharp):
    1.  
    2. half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten){
    3. half NdotL=dot(s.Normal,lightDir);
    4. half4 c;
    5. c.rgb=s.Albedo*_LightColor0.rgb*(NdotL*atten);
    6. c.a=s.Alpha;
    7. return c;
    8. }
    9.  
    .. but that didn't seem to do anything. Even if I were to change "#pragma surface surf Standard" to "#pragma surface surf Lambert"

    I think I understand what each line does, but putting the two concept together don't seem to work. I think I need to flip the coordinates of the light when I flip the normals of the backface but I don't know how to achieve that.

    Any help would be appreciated. Preferably in Deferred lighting but I'll accept any suggestions.

    Thanks.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    So the problem you're having is you're using surface shaders. When you use a surface shader you can't just add "#pragma vertex vert" because surface shaders are actually vertex fragment shader generators. One surface shader generates up to 8 individual vertex fragment shader passes, and they can't all use the same vertex function.

    What you can do instead is inject a custom vertex modifier function into the actual vertex function used by the shaders. For that you'll want to add vertex:vert to the #pragma surface line rather than a new #pragma line. See the "normal extrusion with vertex modifier" example on this page:
    https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    Alternatively you could flip the normal in the surf function, which runs as part of the the fragment shader. But in this case since you're not using normal maps that would be some extra computation. However my preference is actually to do this flip in the fragment shader with a single pass (cull off) shader rather than using two passes. See my example here:
    https://forum.unity3d.com/threads/s...y-shiny-on-the-underside.393068/#post-2572693


    Now, the last bit about custom lighting models and surface shaders ... those don't really work with deferred because deferred is always the standard lighting model. This is a limitation of deferred rendering in general. Using #pragma surface surf Lambert with deferred is just standard specular model with the specular color hardcoded to black.
     
    GixG17 likes this.
  3. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    @bgolus Thank you for the swift reply; I didn't know that about surface shaders.

    If I understand this correctly, you're using VFACE to determine which side the camera is facing and then you flip its normal to accommodate? That's only an alternative to doing two separate passes, correct? I'd still have to do the Lambert to apply lights to "both" sides.

    I had always thought that Deferred rendering was just a particular method of rendering/storing lighting data where you had to write shaders with those limitations in mind, not that Deferred inherently meant that it was a particular standard.

    I'll have to try these tonight and see for myself.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    VFACE will exactly match the culling for back and front faces, unlike a lot of other methods people like to use (like a dot product based on the normal), and it's cheaper because the GPU is just passing the value its already calculated to the fragment shader.

    However I don't understand why you "still have to do the Lambert", you just need to flip the normal and you'll get what you're looking for regardless of what the shading model is.

    I guess that's the real question though, what are you looking for? You say you want a shader that "lights both sides evenly", but that's not to explicit about what you're looking for. Is what you want described by one of these options?

    1. The side facing the light is lit, the side facing away from the light is dark. Rotating the light to point at the other side will flip which face is in light and which is in dark so that only the face towards the light is lit. Similar using a model with doubled geometry.
    2. The side facing the light is lit, the side facing away from the light is also lit. Rotating the light to point at the other side will cause both sides to be dark. Similar to using "Cull Off".
    3. Like option 2, but both sides are always lit equally regardless of which side the light is pointed at. Rotating the light has no effect.

    Option 1 is what my shader does, and what your shader will do if you got the code you have to work.
    Option 2 is what I think you're trying to avoid.
    Option 3 is sometimes referred to a simple translucency shader, like for skin, wax, or thin cloth. The behavior described is how it might be done for a game in the early 2000's, more recently there have been many more accurate / better looking approximations, but the idea is kind of the same of lighting from "behind" a surface still affects it to some extent.
     
  5. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I'm looking for "option 3".

    If VFACE can accomplish this, then yeah I won't "still have to do the Lambert". By looking at the solution you linked, it seemed like VFACE was used merely to realign the normals of the backface. I was more or less fishing for confirmation.

    I guess my biggest issue with writing shaders right now is that I'm not interpreting examples correctly.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    VFACE is just for knowing if you're looking at a backface or not, which in my example I'm using to flip the normal.

    If #3 is what you want, then you want a shading model that outright ignores the light and normal directions. To do that you can't have your shader use Unity's deferred rendering path as that's not something Unity's Standard shading model supports. Some of the shaders you can get on the asset store allow for this in deferred by partially or completely replacing Unity's internal deferred shading shader.

    But that's fine, you can just have a shader that renders using the forward rendering path in conjunction with deferred. Anything transparent already has to do this as deferred can only handle opaque stuff.

    Now back to Lambert. Lambert is a dot product of the lighting direction and the surface normal, the short version is if the surface normal is facing a light it'll be lit, otherwise it won't be. You don't want any of that. You just want the light color and distance / shadow attenuation.
    Code (CSharp):
    1. #pragma surface surf NoDir
    2.  
    3. half4 LightingNoDir(SurfaceOutput s, half3 lightDir, half atten) {
    4.     half4 c;
    5.     c.rgb = s.Albedo * _LightColor0.rgb * atten;
    6.     c.a = s.Alpha;
    7.     return c;
    8. }
    FYI the "Standard shading model" Unity uses isn't "a standard", just the name Unity calls the shading model they use for deferred lighting and its equally named "Standard" shader, aka "the preferred default". They could have named it Bob, but that would have been more confusing...
     
  7. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,500
    Shader books help me to understand how to program shaders in Unity. But In my personal opinion, a visual scripting (add-on from the asset store) helps me in making fast improvement and experiment much faster than traditional scripting. I do not like visual scripting but for shaders works better since is more a visual result and you can preview faster each line of the code (render it).
     
  8. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I tried using VFACE (and practically copy/pasted the example you linked to try and troubleshoot my problem) but it doesn't seem to work. Then again, for me flipping the normals by just multiplying by -1 never seemed to work throughout the numerous attempts at trying to figure this out (this was before coming to the forums and post this thread).

    There's also the issue that LightingNoDir() doesn't seem to output anything at all as nothing I do within it makes any chances to the results.

    I'm getting no luck.
    Code (csharp):
    1.  
    2. Shader "Project/LitDoubleSided" {
    3. Properties {
    4. _TintColor ("Color",Color)=(0.5,0.5,0.5)
    5. _MainTex ("Texture", 2D) = "white" {}
    6. _Cutoff ("Cutoff", float) = 0.5
    7. _Color ("FallbackColor",Color)=(1,1,1,1)
    8. [Enum(Flip,0,Invert,1)] _BumpFlipMode("NormalFlipMode", Float) = 0
    9. }
    10. SubShader {
    11. Tags { "RenderType"= "Opaque" }
    12. Cull Off
    13. LOD 200
    14.  
    15. CGPROGRAM
    16. #pragma surface surf NoDir fullforwardshadows
    17.  
    18. //Useshadermodel3.0target,togetnicerlooking lighting
    19. #pragma target 3.0
    20.  
    21. sampler2D _MainTex;
    22. fixed3 _TintColor;
    23.  
    24. struct Input {
    25. float2 uv_MainTex;
    26. fixed _Cutoff;
    27. fixed facing : VFACE;
    28. };
    29.  
    30. fixed4 _Color;
    31. bool _BumpFlipMode;
    32.  
    33. half4 LightingNoDir(SurfaceOutput s, half3 lightDir, half atten) {
    34. half4 c;
    35. c.rgb = s.Albedo * _LightColor0.rgb * atten;
    36. c.a = s.Alpha;
    37. return c;
    38. }
    39. void surf (Input IN, inout SurfaceOutput o) {
    40. //Albedocomesfromatexturetintedby color
    41. fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    42. o.Albedo = c.rgb * (_TintColor*2);//*2
    43.  
    44. //Metallicandsmoothnesscomefromslider variables
    45. o.Alpha = c.a;
    46.  
    47.   if(IN.facing<0.5)
    48.   {
    49.   if(_BumpFlipMode)
    50.   o.Normal*=-1.0;
    51.    else
    52.   o.Normal.z*=-1.0;
    53. }
    54. }
    55. ENDCG
    56. }
    57.  
    58. FallBack "Diffuse"
    59. }
    60.  
    This is what I see using the shader code above (the two vertical planes have the shader, the "floor" has the basic unity shader):


    And this is when I use "#pragma surface surf Standard" instead of "#pragma surface surf NoDir":

    Note: the frontface of each plane is facing the light source.

    There's something fundamental that I'm missing, here.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Ignore the VFACE stuff for now. Like I said, it's not something you need / want for this since you do not even need the normals for what you want. However it's not working how you expect because you're multiplying the o.Normal but never actually set it to anything, so it's going to be a bogus direction. It's also a snippet of code for use with a normal map, which you don't have, so the bump flip mode option isn't even useful. The

    Try using the following with out the "if facing" stuff:
    o.Normal = half3(0.0, 0.0, IN.facing);

    But this is just to get option 1.

    As for why the LightingNoDir isn't working as I would expect, I'm not entirely sure. I explicitly avoid surface shaders most of the time these days so I might be forgetting something about them, and I'm not at a computer at the moment I can test it on.

    Try copying the LightingSimpleLambert shader from this page in its entirety:
    https://docs.unity3d.com/Manual/SL-SurfaceShaderLightingExamples.html

    Then change the(NdotL * atten)to just atten and see if that works.
     
  10. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I get this:

    (I'm really not recycling the previous image)

    I'd also point out that the "o.Normal = half(0.0, 0.0, IN.facing);" doesn't seem to be doing anything different.

    Code (csharp):
    1.  
    2. Shader "Project/LitDoubleSided" {
    3. Properties {
    4. _TintColor ("Color",Color)=(0.5,0.5,0.5)
    5. _MainTex ("Texture", 2D) = "white" {}
    6. _Cutoff ("Cutoff", float) = 0.5
    7. _Color ("FallbackColor",Color)=(1,1,1,1)
    8. }
    9. SubShader {
    10. Tags { "RenderType"= "Opaque" }
    11. Cull Off//Back
    12. LOD 200
    13.  
    14. CGPROGRAM
    15. #pragma surface surf SimpleLambert fullforwardshadows
    16.  
    17. //Useshadermodel3.0target,togetnicerlooking lighting
    18. #pragma target 3.0
    19.  
    20. sampler2D _MainTex;
    21. fixed3 _TintColor;
    22.  
    23. struct Input {
    24. float2 uv_MainTex;
    25. fixed _Cutoff;
    26. fixed facing : VFACE;
    27. };
    28.  
    29. fixed4 _Color;
    30.  
    31. half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
    32. half4 c;
    33. c.rgb = s.Albedo * _LightColor0.rgb * atten;
    34. c.a = s.Alpha;
    35. return c;
    36. }
    37. void surf (Input IN, inout SurfaceOutput o) {
    38. //Albedocomesfromatexturetintedby color
    39. fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    40. o.Albedo = c.rgb * (_TintColor*2);//*2
    41.  
    42. //Metallicandsmoothnesscomefromslider variables
    43. o.Alpha = c.a;
    44.  
    45. o.Normal = half3(0.0, 0.0, IN.facing);
    46. }
    47. ENDCG
    48. }
    49.  
    50. FallBack "Diffuse"
    51. }
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Lighting using the "NoDir" function:
    lighting_nodir.png
    Lighting does not change based on surface normal, only distance.
    Code (CSharp):
    1. Shader "Custom/NoDirShading" {
    2.     Properties {
    3.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.         Cull Off
    9.      
    10.         CGPROGRAM
    11.         #pragma surface surf NoDir
    12.  
    13.         // Use shader model 3.0 target, to get nicer looking lighting
    14.         #pragma target 3.0
    15.  
    16.         fixed4 LightingNoDir( SurfaceOutput s, half3 lightDir, fixed atten)
    17.         {
    18.             return fixed4(s.Albedo * _LightColor0.rgb * atten, 1.0);
    19.         }
    20.  
    21.         sampler2D _MainTex;
    22.  
    23.         struct Input {
    24.             float2 uv_MainTex;
    25.         };
    26.  
    27.         void surf (Input IN, inout SurfaceOutput o) {
    28.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    29.             o.Albedo = c.rgb;
    30.         }
    31.         ENDCG
    32.     }
    33.     FallBack "Diffuse"
    34. }
    35.  

    Lighting using Lambert and VFACE:
    lighting_vface.png
    Lighting only shows on the side facing the light.
    Code (CSharp):
    1. Shader "Custom/VFACEShading" {
    2.     Properties {
    3.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.         Cull Off
    9.      
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.         #pragma target 3.0
    13.  
    14.         sampler2D _MainTex;
    15.  
    16.         struct Input {
    17.             float2 uv_MainTex;
    18.             float facing : VFACE;
    19.         };
    20.  
    21.         void surf (Input IN, inout SurfaceOutput o) {
    22.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    23.             o.Albedo = c.rgb;
    24.             o.Normal = half3(0.0, 0.0, IN.facing);
    25.         }
    26.         ENDCG
    27.     }
    28.     FallBack "Diffuse"
    29. }
    30.  

    Lighting with Lambert and otherwise just Cull Off:
    lighting_culloff.png
    Lighting is always based on the "front" face's normal.
    Code (CSharp):
    1. Shader "Custom/CullOffShading" {
    2.     Properties {
    3.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.         Cull Off
    9.      
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.         #pragma target 3.0
    13.  
    14.         sampler2D _MainTex;
    15.  
    16.         struct Input {
    17.             float2 uv_MainTex;
    18.         };
    19.  
    20.         void surf (Input IN, inout SurfaceOutput o) {
    21.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    22.             o.Albedo = c.rgb;
    23.         }
    24.         ENDCG
    25.     }
    26.     FallBack "Diffuse"
    27. }
    28.  

    The scene is 3 quads with their "fronts" facing towards the center, and a plane using the default material for the floor. The light is in the middle of the three quads in the first image, then to the right of them.

    I also tried your shader in your post and it worked exactly like the first shader I have in this post. If you try these shaders and don't get similar results, I don't know what to tell you.
     
  12. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    PixelLight count was set to Zero...

    ...

    ... I feel stupid.

    I re-tested everything that's been discussed so far, including some of my previous attempts prior to coming in to the forums and they're all working. Not necessarily working as I wanted, but at least it makes a hell of a lot more sense.

    The NoDir shader is exactly the effect I've been wanting; I'll make further adjustments to it now.

    @bgolus Thanks a lot! Not only were you instrumental at helping me solve this, but you've explained things that help me understand a little bit more as to what's going on under the hood.

    @AlanMattano Thanks for sharing. I'll definitely pick up some textbook for more study.
     
    bgolus likes this.
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    There are several threads on this forum with links to resources on learning to write shaders. I tend to point people to Alan Zucconi's tutorials, starting here:

    http://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d/

    Go through all 5 sections, is a nice crash course for a basic understanding of technical side of how shaders work and the most common usages in Unity. From there he has several more tutorials on more complex topics, both related to share and not, as well as c#. Some of the threads on the forum point to other sites as well, like cat like coding, which goes a bit heavier into the actual mathematics and specifics of some of the "magic" functions you'll see in most official Unity shaders.
    http://catlikecoding.com/unity/tutorials/

    Shader books can be good too, though these days I find there's enough content available for free* online to make books less useful.

    * Don't forget to give money (if you can) to the places you find useful so they can keep making content!
     
    GixG17 likes this.