Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Spotlight shadows in custom vert/frag shader

Discussion in 'Shaders' started by Jeremy_35, Nov 4, 2016.

  1. Jeremy_35

    Jeremy_35

    Joined:
    Sep 13, 2013
    Posts:
    6
    Hello everyone,

    I am trying to develop a custom shader that receives shadows.

    I have tried this solution : https://alastaira.wordpress.com/201...-unity-vertexfragment-shader-in-7-easy-steps/

    It is based on the functions provided in ""AutoLight.cginc" :

    Code (CSharp):
    1. LIGHTING_COORDS(0,1) // in vertex output struct
    2.  
    3. TRANSFER_VERTEX_TO_FRAGMENT(o); // in vertex shader
    4.  
    5. float attenuation = LIGHT_ATTENUATION(i); // in fragment shader
    This solution works great, however my objects only receive the shadows from the main directionnal light.
    Is there any way to also get also the shadows from the spotlights and pointlights?

    I have tried to add the following line to my shader but the result is the same :
    Code (CSharp):
    1. #pragma multi_compile_fwdadd_fullshadows
    Moreover, I wonder if there is any way that my objects receive the shadow attenuation value provided by a single light (a spotlight) and not by the other ones?

    Thank you.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Unity does its lighting in multiple passes, so if you only have one pass (fwdbase) you only get the directional light & shadows with the other lighting baked into per vertex lighting or the ambient lighting. Vertex lighting and ambient don't support shadowing, so even if you have that working so you see spot lights you won't get shadows from then.

    Instead you need to add a second pass that uses the multi_compile_fwdadd_fullshadows with some minor adjustments vs the fwdbase pass. I would suggest making a simple surface shader and looking at the generated shader code (select the surface shader, click on "show generated") and deconstruct what its doing. There will be a ton of passes, forward base, forward add, deferred, prepass, meta, and maybe a shadow caster depending on your surface shader, but only the first two do you need to care about.
     
    sirleto and alexey_maximuk like this.
  3. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    I've been looking for the same thing for a while now but I don't have access to generated code. Previously I tried looking in the AutoLight.cginc file for information on forward and differed.
    If anyone has found something in the generated code could you start a topic and sticky it so it can be a resource for future users.
     
  4. Blackhart

    Blackhart

    Joined:
    May 2, 2015
    Posts:
    10
    You can also download the standard shader code from the Unity website. It perform all the available passes.
     
  5. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    Yes but it doesn't give the generated code from the shader. I'm going to look around some of the include file to see if I can find anything that might give a clue as to how spotlight shadows are called in forward rendering.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    There's generated code for surface shaders, not for the standard shader or vertex fragment shaders in general. There's compiled shaders, but that doesn't give you much information either unless you can read shader assembly, though it does give you a look at the keywords the shader is compiled using.

    The main thing for understand how spotlights in Unity work is looking at the standard shader or the generated shader code from a very simple surface shader and look at the forward add pass. At the most basic level you just need to make a copy of the forward base pass and change the Tags { "LightMode"="ForwardBase" } to "ForwardAdd", though there's a couple other settings that are needed to get it looking right.
     
  7. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    I looked at the diffuse shader generated code with and without fullshadows. There is no difference in the code.
    fwdadd_fullshadows must effect something outside of the shader.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    It changes the keywords used to compile with.
     
  9. Blackhart

    Blackhart

    Joined:
    May 2, 2015
    Posts:
    10
  10. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    Still that would not explain why when I remove the fullshadows keyword I get the exact same code. the only thing that changes is that I either do or do not have a point light shadow.

    Wait, so you saying that instead of looking at the generated code I need to look at the compiled code. -_-
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Surface shaders are vertex / fragment shader generators, and vertex / fragment shaders are not the final code used by the GPU to render. The various #pragma multi_compile and #pragma shader_feature lines are both used to tell the shader compiler to make multiple versions of the shader with different features turned on and off (variants). If you look at the shader code you'll see #if or #ifdef or #ifndef lines, and these are bits of shader code that will be ignored or used based on the keywords.

    For example, if you have a shader with

    #pragma shader_feature MAKERED

    and

    fixed4 color = fixed4(1,1,1,1); // white
    #ifdef MAKERED
    color = fixed4(1,0,0,1); // red
    #endif

    Unity will compile two versions of the shader, one where the color value is white, and another which overrides it with red, and that can be toggled via code or a material parameter.

    So the bit you're missing is #pragma multi_compile_fwdadd vs multi_compile_fwdadd_fullshadows is the later includes the keywords that enable shadowed lights, whereas the prior does not and lights that have shadows will fallback to the non-shadowed variants of the shader.
     
    Blackhart likes this.
  12. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    I've looked over the generated code and extracted the first and second forward rendering passes. Next I striped out everything. I am convinced that spotlight and point shadows are some sort of magic because I see nothing in this shader that should still be creating a shadow. I know the magic is in the second pas but I can't tell where.
    Code (CSharp):
    1.  
    2. properties
    3. {
    4. _MainTex("Base (RBG)", 2D) = "white" {}
    5. _Ramp2D("BRDF Skinlights",2D)= "blue"{}
    6.  
    7.  
    8. }
    9. SubShader
    10. {
    11.  
    12. Tags{"RenderType" = "Opaque"}
    13. LOD 200
    14.  
    15.  
    16.     // ------------------------------------------------------------
    17.     // Surface shader code generated out of a CGPROGRAM block:
    18.    
    19.  
    20.     // ---- forward rendering base pass:
    21.     Pass {
    22.         Name "FORWARD"
    23.         Tags { "LightMode" = "ForwardBase" }
    24.  
    25. CGPROGRAM
    26. // compile directives
    27. #pragma vertex vert_surf
    28. #pragma fragment frag_surf
    29. #pragma target 3.0
    30. #pragma multi_compile_fwdbase
    31. #define UNITY_PASS_FORWARDBASE
    32. #include "UnityCG.cginc"
    33. #include "Lighting.cginc"
    34. #include "AutoLight.cginc"
    35.  
    36.  
    37.  
    38.  
    39.  
    40.  
    41. struct Input
    42. {
    43. float2 uv_MainTex;
    44. };
    45.  
    46.  
    47. // vertex-to-fragment interpolation data
    48. #ifdef LIGHTMAP_OFF
    49. struct v2f_surf {
    50.   float4 pos : SV_POSITION;
    51.   fixed3 normal : TEXCOORD0;
    52. };
    53. #endif
    54. #ifndef LIGHTMAP_OFF
    55. struct v2f_surf {
    56.   float4 pos : SV_POSITION;
    57. };
    58. #endif
    59. #ifndef LIGHTMAP_OFF
    60.  
    61. #endif
    62.  
    63. // vertex shader
    64. v2f_surf vert_surf (appdata_full v) {
    65.   v2f_surf o;
    66.   o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    67.   return o;
    68. }
    69.  
    70.  
    71. // fragment shader
    72. fixed4 frag_surf (v2f_surf IN) : SV_Target {
    73.   // prepare and unpack data
    74.  
    75.   // compute lighting & shadowing factor
    76.  
    77.   fixed4 c = 0;
    78.   return c;
    79. }
    80.  
    81. ENDCG
    82.  
    83. }
    84.  
    85.     // ---- forward rendering additive lights pass:
    86.     Pass {
    87.         Name "FORWARD"
    88.         Tags { "LightMode" = "ForwardAdd" }
    89.         ZWrite Off Blend OneMinusDstColor One  Fog { Color (0,0,0,0) }
    90.  
    91. CGPROGRAM
    92. // compile directives
    93. #pragma vertex vert_surf
    94. #pragma fragment frag_surf
    95. #pragma target 3.0
    96. #pragma multi_compile_fwdadd_fullshadows
    97. #define UNITY_PASS_FORWARDADD
    98. #include "UnityCG.cginc"
    99. #include "Lighting.cginc"
    100. #include "AutoLight.cginc"
    101.  
    102.  
    103. sampler2D _MainTex;
    104. sampler2D _Ramp2D;
    105.  
    106. struct Input
    107. {
    108. float2 uv_MainTex;
    109. };
    110.  
    111. half4 LightingSkinlights(SurfaceOutput r, half3 lightDir)
    112. {
    113.  
    114.    
    115.     float4 s;
    116.    
    117.     return s;
    118.    
    119. }
    120.  
    121. void surf (Input IN, inout SurfaceOutput t)
    122. {
    123. half4 s = float4(.5, .5,.5, 1);
    124. t.Albedo = s.rgb;
    125. t.Alpha = s.a;
    126.  
    127.  
    128. }
    129.  
    130.  
    131.  
    132.  
    133.  
    134. // vertex-to-fragment interpolation data
    135. struct v2f_surf {
    136.   float4 pos : SV_POSITION;
    137.   fixed3 normal : TEXCOORD0;
    138.   half3 lightDir : TEXCOORD1;
    139.   //SHADOW_COORDS(4) // for shadows wont work
    140.   LIGHTING_COORDS(2,3)
    141. };
    142.  
    143. // vertex shader
    144. v2f_surf vert_surf (appdata_full v) {
    145.   v2f_surf o;
    146.   o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    147.   o.normal = mul((float3x3)_Object2World, SCALED_NORMAL);
    148.   float3 lightDir = WorldSpaceLightDir( v.vertex );
    149.   o.lightDir = lightDir;
    150.  
    151.   // pass lighting information to pixel shader
    152.   TRANSFER_VERTEX_TO_FRAGMENT(o);
    153.   TRANSFER_SHADOW(o);//for shadows
    154.   return o;
    155. }
    156.  
    157. // fragment shader
    158. fixed4 frag_surf (v2f_surf IN) : SV_Target {
    159.   // prepare and unpack data
    160.   #ifdef UNITY_COMPILER_HLSL
    161.  
    162.   #else
    163.   Input surfIN;
    164.   #endif
    165.   #ifdef UNITY_COMPILER_HLSL
    166.   SurfaceOutput o = (SurfaceOutput)0;
    167.   #else
    168.   SurfaceOutput o;
    169.  
    170.   o.Normal = IN.normal;
    171.  
    172.   // call surface function
    173.   surf (surfIN, o);
    174.   #ifndef USING_DIRECTIONAL_LIGHT
    175.   fixed3 lightDir = normalize(IN.lightDir);
    176.   #else
    177.   fixed3 lightDir = IN.lightDir;
    178.   #endif
    179.   fixed4 c = LightingLambert (o, lightDir, SHADOW_ATTENUATION(IN));//LIGHT_ATTENUATION(IN));
    180.   c.a = 0.0;
    181.   return c;
    182. }
    183.  
    184. ENDCG
    185.  
    186. }
    187. }
    188.  
    189.  
    190. Fallback "Diffuse"
    191.  
    192.  
    193.  
    194. }

    I switched LIGHT_ATTENUATION with SHADOW_ATTENUATION and added TRANSFER_SHADOW(o) and it still worked
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    You mean apart from the SHADOW_ATTENUATION macro, which is rather explicitly named as something that might be related to shadows?

    Look at AutoLight.cginc and look at what SHADOW_ATTENUATION and LIGHT_ATTENUATION do.
     
  14. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    Just making things clear

    1) This shader was produced from the generated code of a surface shader; then gutted. What left of the code is necessary to keep the shader from being pink. There isn't even a functional lighting function.

    2) I just wrote that I was the one that switched it from LIGHT_ATTENUATION to SHADOW_ATTENUATION and there was no change in the shader or shadows. I've already been through AutoLight.cginc, that one of the things I mentioned earlier.

    3) There is nothing I see in this shader that isn't in ever other fragment shader that supports directional shadows. Yet this shader supports point light shadows too. For example autolight, unitycg, lighting cords, light or shadow attenuation.

    4) when searching the net, there is no indication that anyone has found a way to get point light or spot light shadows int a fragment shader. So if it was so easy as just looking at the includes it would have been done by now.
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    But there is a lighting function. In the Foward Add pass, which is used to render additional lights like spot lights and point lights, you're calling LightingLambert() which is the lighting function used by the built in diffuse shader.

    2) Because the light attenuatuon function in some cases just calls the shadow attenuatuon macro and does nothing else. When it comes to shadows, they're effectively the same function. If you actually look at the AutoLight.cginc you'd see that.

    3) Except for this shader has the forward add pass, where most basic shaders out there do not.

    4) What you posted is just a vertex fragment shader. It just happens to be one generated by a surface shader and is therefore a bit messy. I would guess a vast number of the popular shaders on the asset store are vertex fragment shaders and support point and spot shadows, like Uber Standard, or RTP, or anything made using Shader Forge. I almost never use surface shaders myself because I'm usually looking for more control than is possible with a surface shader, though I have on occasion started with a surface shader's generated code like you posted above.

    One last thing is that shader has the line Fallback "Diffuse" which if almost everything else in your shader is removed would mean it would continue to work since that shader has support for directional lighting and point / spot lights and your shader would use the passes from that shader if they were missing from yours. That's why your shader casts shadows currently even though you don't have a shadow caster pass.
     
  16. IronMathbook

    IronMathbook

    Joined:
    Apr 12, 2014
    Posts:
    60
    2) The problem is you can't just take thing out of AutoLight.cginc and get them to work in your shader. So it's a mystery what the surface shader is really calling on.

    3) I can except this but for my shader I have a forward add pass too.

    4) I have gutted the first and second pass surface shader but Fallback "Diffuse" is not being called unless a shader can call Fallback while still using what's left of the gutted code. So that leaves the last option that even though there was a lighting function in the surface shader originally it was phoning in another lighting function all this time anyway?

    I can only suspect that nothing in that shader is using #pragma multi_compile_fwdadd_fullshadows and is instead passing this keyword to the LightingLambert model found in Lighting.cginc. But no I still do not know how the spotlight and point light shadows are accessed.
    I think Lighting.cginchas something to do with it because it's an include that i don't see as much.