Search Unity

Surface shader - offset shadows on z axis?

Discussion in 'Shaders' started by fujizayana, Apr 27, 2020.

  1. fujizayana

    fujizayana

    Joined:
    Apr 2, 2018
    Posts:
    9
    Hi all,
    I'm working on a game using sprites in a 3d environment that cast and receive shadows. I am using a modified surface shader I found online to do this.

    What I am wondering is if it is possible to modify the position of the shadows so they are offset from the objects casting them, specifically along the Z axis? For example, if I have a sprite of a large rock, because the sprite is in a 2.5d orientation the sprite implies depth of the object, but the shadow starts at the base of the sprite because it's simply a billboarded sprite rather than a 3d object. What I'd like is to be able to make the shadow offset by some amount on the z axis so it appears to start further back, to further give the appearance of it having depth.

    I also do not want this to be uniform for all objects, I would make a few variants of the shader with varying offsets and use them for different types of objects, so I cannot just set the bias of the light sources

    I assume I would have to edit addshadows and/or fullforwardshadows to be something custom I define in the shader, but after hours of looking I cannot find how to do this.

    Any help would be greatly appreciated :)
     
  2. AnKOu

    AnKOu

    Joined:
    Aug 30, 2013
    Posts:
    123
    My first idea I have to change the position of the shadow would be to add a shadow caster pass to your shader where you could modify the position of your vertices in the vertex program. (or even in a geometry program if needed...)

    Code (CSharp):
    1. // shadow caster rendering pass, implemented manually
    2.         // using macros from UnityCG.cginc
    3.         Pass
    4.         {
    5.             Tags {"LightMode"="ShadowCaster"}
    6.  
    7.             CGPROGRAM
    8.             #pragma vertex vert
    9.             #pragma fragment frag
    10.             #pragma multi_compile_shadowcaster
    11.             #include "UnityCG.cginc"
    12.  
    13.             struct v2f {
    14.                 V2F_SHADOW_CASTER;
    15.             };
    16.  
    17.             v2f vert(appdata_base v)
    18.             {
    19.                 v2f o;
    20.                 //object space
    21.                 //v.vertex.z += 1000.;
    22.                 TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    23.                 return o;
    24.             }
    25.  
    26.             float4 frag(v2f i) : SV_Target
    27.             {
    28.                 SHADOW_CASTER_FRAGMENT(i)
    29.             }
    30.             ENDCG
    31.         }
    https://forum.unity.com/threads/custom-shadow-pass-for-surface-shader.607642/

    But I'm not sure I understood your issue correctly and if modifying shadow like that is really a good solution...

    [edit] add offset on object space instead of clip space.:oops:
     
    Last edited: Apr 28, 2020
  3. fujizayana

    fujizayana

    Joined:
    Apr 2, 2018
    Posts:
    9
    Thankyou so much for your reply! I have made it work, for some reason the displacement of the shadow needs to be on the Y axis in the shader to move on the Z axis in worldspace, but the displacement works!

    It unfortunately, does not produce shadows that take the transparency of the sprite into account, for both casting and receiving shadows, whereas the original shader does. The shadows this pass make are just rectangular blocks.

    I don't suppose you know how to make these shadows take transparency of sprites into account?
     
  4. AnKOu

    AnKOu

    Joined:
    Aug 30, 2013
    Posts:
    123
    You can add cut-out to the shadow caster so that it won't cast a quad anymore. The idea is to use the clip function which does not render a pixel if the given arg is under 0.

    Code (CSharp):
    1.  
    2. uniform sampler2D _MainTex;
    3. uniform fixed _Cutoff;
    4. uniform fixed4 _Color;
    5.  
    6. float4 frag( v2f i ) : SV_Target
    7. {
    8.     fixed4 texcol = tex2D( _MainTex, i.uv );
    9.     clip( texcol.a*_Color.a - _Cutoff );
    10.  
    11.     SHADOW_CASTER_FRAGMENT(i)
    12. }
    You will need to change the vertex program as well to get uv coord. It is available in the github shader here :
    https://github.com/PushyPixels/BreakfastWithUnity/blob/master/Assets/Built-In Shaders/DefaultResourcesExtra/AlphaTest-VertexLit.shader
     
    Last edited: Apr 28, 2020
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Ooph... okay, wait ... don’t do this. I mean, @AnKOu ’s suggestion isn’t wrong for “Offset on the z”, but you didn’t specify which Z. Also that particular method is especially finicky because the value you offset by doesn’t mean what you think it means, and depends on things like the near & for clip planes, how far away the object is from the camera, and even what platform you’re rendering on. Directly modifying clip space positions is hairy business unless you know exactly what’s up. Let’s not go into that right now.

    If you want to offset something in world space, then you need to calculate the vertex’s’ world space position, do the offset there, and transform it back. Or, slightly faster, calculate the object space offset. You can also do that using a Surface Shader without needing a custom shadow caster pass, meaning it’ll respect whatever else you’re doing in the shader that might affect shadows.

    The key parts are:
    Have the Surface Shader generate a shadow caster pass with
    addshadow
    on the
    #pragma surface
    line.
    Use a custom vertex function with
    vertex:vert
    , also on the
    #pragma surface
    line.
    Offset the object vertex space position in the vertex function, but only when it’s the shadow caster pass for the shadow maps.

    Most of this is easy. The first part is a literal copy & paste and you’re done. The second there are examples in Unity’s documentation for doing vertex offsets, though not exactly this use case. And running special code only in the shadow caster pass is easy enough by using preprocessor macro
    #ifdef
    statements.

    The ugly part is detecting if you’re rendering a shadow map or not. No really. The shadow caster pass gets used for more than just the shadow map, and there’s no 100% guaranteed way to detect what it is being used to rendered. We’ll come back to this.


    So, first thing is offsetting in world Z. You’ll need a basic custom vertex function of which you can find in the above documentation and add this to it.
    Code (csharp):
    1. v.vertex.xyz += mul((float3x3)unity_WorldToObject, float3(0.0, 0.0, _ZOffset);
    That takes a world space vector, which is just that Z offset distance in a
    float3
    , and transforms it from world space into object space. You’re then adding that to the object space vertex position and you’re done...ish.

    Next is limiting it to the shadow caster pass. If you looked at the preprocessor link above it has a list of defines for surface shade passes. So you just need to wrap the above line of code in:
    Code (csharp):
    1. #ifdef UNITY_PASS_SHADOWCASTER
    2. // that vertex offset line above
    3. #endif
    And then that last part. Unity uses the shadow caster pass to generate the camera depth texture. This is important because that’s also what the main directional light casts shadows onto. So if you offset that, where the shadows are received move too. The best way to do it, and the way Unity’s own code determines what is being rendered, is something like this bit of code:
    Code (CSharp):
    1. #ifdef SHADOWS_DEPTH
    2. if (unity_LightShadowBias.z != 0.0) {
    3.     // shadow
    4. } else {
    5.     // camera depth
    6. }
    7. #endif
    https://forum.unity.com/threads/dif...d-shadow-receiver-in-5-2.362653/#post-2353713

    It remains the best way to do it, but that’s unfortunate because that only works if you don’t have a light with a bias of 0.0. Realistically you should never have a light with a bias of 0.0, but just know if you do everything here will break.
     
  6. fujizayana

    fujizayana

    Joined:
    Apr 2, 2018
    Posts:
    9
    Thankyou so much to both of you for your help with this! My shader knowledge isn't great, so I really appreciate going from not knowing to do this to being currently almost finished. One last struggle I'm coming up against, and it's a weird one...

    I've currently got this. Shader:

    and script to pass per object data, so for objects I want to have offset sprites, I can do so by changing a float in the editor:

    Now, the weird problem: this material, whether offset or not, cannot receive shadows from point lights, only directional lights. I'm totally at a loss.

    I know there was a mention in the thread bgolus linked to about point lights in the #ifdef SHADOWS_DEPTH bit, but trying both options in that thread doesn't provide the right results.

    Any ideas? I unfortunately need point and directional lights in my game.

    Thanks again so much, you've both been so much help! :)
     
  7. fujizayana

    fujizayana

    Joined:
    Apr 2, 2018
    Posts:
    9
    Update: I realised that adding fullforwardshadows to the pragma in the surface shader gives the ability to receive shadows from point lights, but then it occured that point light shadows weren't being offset. Is there a way this shader can apply to point lights as well as directional lights?

    Again, thanks so much for all your help!
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That’s the keyword shadow caster passes have when rendering to the camera depth texture or the main directional light shadow maps. You want to always do the offset if
    UNITY_PASS_SHADOWCASTER
    is defined, except when
    SHADOWS_DEPTH
    is also defined and
    unity_LightShadowBias.z
    is zero. I’ll leave you to figure out how to rearrange your code to do that.