Search Unity

Access _ShadowMapTexture in Surface Shader

Discussion in 'Shaders' started by Quinzen, Jan 26, 2019.

  1. Quinzen

    Quinzen

    Joined:
    Jan 26, 2019
    Posts:
    3
    Hello there,

    I recently found a nice vertex/fragment shader that uses the _ShadowMapTexture provided from the AutoLight.cginc to add outlines to shadows. I've tried to implement this in my Surface Shader, but it appears that a) I need to put my code in a #if defined(SHADOWS_SCREEN) block to make sure _ShadowMapTexture is defined, and b) even then it appears there are no values in it, so I can't use it. Is that a limitation of Surface Shaders?
    The only thing I could think of is getting the ammount of shadow casted upon the object, but that would only be a float so I can't compare it to neighbouring pixels to check if it's a edge or not.
    And yes I know Image Effect Shaders would be really usefull for this, but if possible I'd like to have it in the Surface Shader.
    Hopefully someone can help me solve it or point out why I can't access it from a Surface Shader.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    I suspect the issue is less that _ShadowMapTexture is empty, and more that you're using IN.screenPos inside a #if.

    Unity's surface shaders are ... funny. As part of its code generation it looks at the surface function and tries to figure out what is actually being used, removing or otherwise not calculating stuff that it thinks isn't being used. Unfortunately it's a bit over zealous at times. In this case having the use of IN.screenPos inside an #if will make it decide it is never needed and never actually set it.

    There are a few ways around this from ugly hacks to more thorough work arounds. The simplest hack is to use the value someplace else in a way that does as little harm as possible. Like this.

    o.Albedo = //whatever you albedo color is
    o.Albedo += IN.screenPos * 0.00001;

    It's stupid and ugly, but will trick the compiler into thinking it's being "used".
     
  3. Quinzen

    Quinzen

    Joined:
    Jan 26, 2019
    Posts:
    3
    Thanks for the quick reply. I guess I should've just included the relevant code in my post but this is what my code looks like, I don't use IN.screenPos at all.
    Code (CSharp):
    1. fixed4 col = fixed4(1, 0, 0, 1);
    2.             float3 shadowCoord = IN._ShadowCoord.xyz / IN._ShadowCoord.w;
    3.  
    4.             float shadowmap = tex2D(_ShadowMapTexture, shadowCoord.xy);
    5.             float thickness = 20.;
    6.             float e = sampleEdge(_ShadowMapTexture, shadowCoord.xy, thickness / IN._ShadowCoord.w);
    7.             col.xyz = lerp(pow(col.xyz, 3.6)*0.45, col.xyz, shadowmap);
    8.             col.xyz = lerp(col.xyz, float3(.1, 0.2, 0.3), e);
    9.            
    10.             o.Albedo = col;
    I use the UNITY_SHADOW_COORDS macro to get the IN._ShadowCoord values but based on your answer, that could also be the issue, so I tried o.Albedo = IN._ShadowCoord.xyz; but it only made the entire thing black, so I guess it could also be that both coords and texture aren't usable the way I want to in a surface shader?
    (the sampleEdge function is just a simple check to compare the values from the surrounding points to detect if its a edge, no shadow, or point in the shadow)
    But good to know that I need to keep that in mind when using conditional parts, thanks!
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    I'm assuming you're just adding UNITY_SHADOW_COORDS to the Input struct? That just defines the type & name of the variable, but doesn't fill it with any information. You need to use a custom vertex function to calculate and pass that from the vertex to the fragment, and eventually to the surface function. However the relevant macro for doing so, UNITY_TRANSFER_SHADOW, won't really work with Surface Shaders without a lot of redundant work.

    Now I'm going to assume you're looking to do this only to the main directional light's shadows, I'd recommend just using screenPos and the hack I mentioned above.

    Code (CSharp):
    1. Shader "Custom/SurfaceShadowEdge" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.  
    10.         CGPROGRAM
    11.         #pragma surface surf Standard fullforwardshadows
    12.         #pragma target 3.0
    13.  
    14.         sampler2D _MainTex;
    15.  
    16.         struct Input {
    17.             float2 uv_MainTex;
    18.             float4 screenPos;
    19.         };
    20.  
    21.         fixed4 _Color;
    22.  
    23.         float sobel(sampler2D _tex, float2 uv, float2 offsetScale)
    24.         {
    25.             float2 uvOffsets[8] = {
    26.                 float2(-1,-1),
    27.                 float2( 0,-1),
    28.                 float2( 1,-1),
    29.                 float2(-1, 0),
    30.                 float2( 1, 0),
    31.                 float2(-1, 1),
    32.                 float2( 0, 1),
    33.                 float2( 1, 1)
    34.             };
    35.  
    36.             float samples[8];
    37.             for (int i=0; i<8; i++)
    38.                 samples[i] = tex2D(_tex, uv + uvOffsets[i] * offsetScale).r;
    39.  
    40.             float x = samples[0] + 2 * samples[3] + samples[5] - samples[2] - 2 * samples[4] - samples[7];
    41.             float y = samples[0] + 2 * samples[1] + samples[2] - samples[5] - 2 * samples[6] - samples[7];
    42.             return sqrt(x*x + y*y);
    43.         }
    44.  
    45.         void surf (Input IN, inout SurfaceOutputStandard o) {
    46.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    47.             o.Albedo = c.rgb;
    48.             o.Albedo += IN.screenPos * 0.000001;
    49.  
    50.     #if defined(SHADOWS_SCREEN)
    51.             float2 shadowUV = IN.screenPos.xy / IN.screenPos.w;
    52.             o.Albedo *= 1 - saturate(sobel(_ShadowMapTexture, shadowUV, 1.0 / _ScreenParams.xy));
    53.     #endif
    54.         }
    55.         ENDCG
    56.     }
    57.     FallBack "Diffuse"
    58. }
     
    tyoukabun and sirleto like this.
  5. Quinzen

    Quinzen

    Joined:
    Jan 26, 2019
    Posts:
    3
    That works how I need it, thanks alot! Also thanks for beeing so active here, found your answers in a couple threads even back to 2013 that saved me from quite a few issues now :D