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

Can a shader do shadow casting, don't write to depth texture, but still perform ztest?

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

  1. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    This might sound like a strange question but it's something I had a hard time figuring out:

    - In built-in pipeline + forward rendering, if you have a pass with LightMode=ShadowCaster, it will be called in both depth texture pass and shadow caster pass.

    - With MeshRenderer you can disable shadow casting, so you can have an object that write to depth but don't cast shadow.

    - But the reverse is more difficult, if you want shadow casting then you need a shadow caster pass, but to disable writing to depth texture, you don't have a flag to do so.

    - Technically SetShaderPassEnabled should work, but it would probably disable both depth and shadow caster pass (I haven't been able to use it to disable a ShadowCaster pass, so "probably").

    - ZWrite off does disable the depth pass, but it also affects ZTest.

    In short, is what I want not possible in built-in pipeline? I believe SRP gave us more control, but unfortunately our project is not on SRP.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    If I get this right you want something that:
    * Doesn't render to the camera depth texture
    * Does cast shadows
    * Does still render "normally"?

    Make it use the transparent queue. Transparent objects don't rendering into the depth texture, but still cast shadows, and you can leave
    ZWrite
    and
    ZTest
    working. The caveat is it'll no longer receive shadows as that requires it renders to the depth texture for the main directional light on desktop, and Unity simply doesn't support passing the shadow maps for additional lights to transparent objects. However all this should work fine for the SRP since they also don't render transparent objects to the camera depth texture, but they can receive shadows.
     
  3. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    Thx, my shader is mainly aimed at grasses, which I do want shadow receive, I think the next best options will be for grass to not cast shadow (so we can have no depth + no shadow + can zwrite/ztest + can receive shadow).

    For trees, I will just keep the shadow caster pass so that they both write to depth and cast shadow.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Like I mentioned above, if a shader doesn’t write to the camera depth texture, it can’t receive the main directional light shadows on desktop & consoles. This is because Unity casts the main directional light shadows on the camera depth texture to generate a screen space shadow mask rather than sampling the shadow map in the object shader directly. Otherwise your shader can still sample the screen space shadow mask, but it’ll be the shadows cast on the objects “behind” the grass and not on the grass itself. There’s no way in Unity to cleanly have a shader that doesn’t cast shadows, but does receive shadows since there’s no way to 100% detect if the shadow caster pass is being used for the camera depth texture or a shadow map pass. You generally want to rely on the mesh renderer settings, though if this is terrain grass that gets harder to do.

    There is work around to the shadow receiving thing, which requires copying off the shadow map references and using a custom shader that samples them directly, and there’s a post I made years ago about the closest you can get to detecting shadow caster being used for the camera depth pass, but with a bunch of caveats.

    https://forum.unity.com/threads/no-shadows-visible-on-transparency-shaders.9909/page-3#post-4778408
    https://forum.unity.com/threads/dif...dow-caster-and-shadow-receiver-in-5-2.362653/
     
  5. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    Yeah, thx for reminding me of it, I conveniently forgot that :)
     
  6. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    Hi bgolus

    If you don't mind me going back to this question, I was recently implementing a water shader that receives shadows but don't write to depth (because it does depth-related effects itself.)

    I saw the implementation from interior mapping repo, it seems like we had to store the shadow map (eg. light-space depth texture) separately in order to receive shadow.

    That's the only solution we have for built-in pipeline, as of 2019.4, right?

    Thx!
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Correct.

    A very important aspect of the way the interior mapping project handles this is it does not make a copy of the shadow map. It only ensures the shadow map that already exists is accessible by all shaders. This means it's not using any more memory on the GPU than what the built in shadows were already using.

    Also, because it bares repeating, the depth texture is not the depth buffer. You can write to the depth buffer without rendering to the depth texture, and vice versa. As this case is for water, it likely should be part of the transparent queue range for sorting, in which case it won't render to the depth texture anyway. And the interior mapping shadow script & shader code should allow you to receive the main directional light's shadows.
     
    bitinn likes this.
  8. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    One follow up question:

    Are you aware of a keyword that can tell the shader "shadow pass have been skipped due to all objects is outside of max shadow distance"?

    We are seeing some interesting artifact (a black circle) when I make the shadow map texture available but goes out of max shadow distance range (when there is no shadow pass).

    I wonder if there is a way for shader to detect that and ignore the shadow map at that point.

    Thx!
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    For objects that are opaque and already receive shadows normally? Yes.
    By default any opaque shadow receiving object switches to a "no shadow" variant when too far away.

    For objects that are transparent and as far as Unity is concerned can't receive shadows? No.
    Well, yes ... but it's the variant they're already using since any range is the "not receiving shadows" range for transparent objects.

    I know there's a long time bug with a single "shadow cascade" were this occurs even on opaque objects, but I haven't seen it happen when using Gaxil's shadow code. It should be possible to figure out if the current position is outside the range using the
    unity_ShadowSplitSpheres
    and
    unity_ShadowSplitSqRadii
    properties.
     
    bitinn likes this.
  10. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    I am driving myself slightly crazy with unity_ShadowSplitSpheres and unity_ShadowSplitSqRadii.

    I thought unity_ShadowSplitSpheres[3].w * unity_ShadowSplitSpheres[3].w == unity_ShadowSplitSqRadii.w == Max Shadow Distance. But they were not. I tried to compare square of View Distance with them, only directly passing Max Shadow Distance works fully as expected.

    To me dot(unity_ShadowSplitSqRadii, 4.0) seems close, it solves our problem (can cull shadow correctly), but it doesn't feel quite right.
     
  11. DoubleThreeZhang

    DoubleThreeZhang

    Joined:
    Aug 14, 2017
    Posts:
    10
    Hi, man. I recently meet the same problem of my water shader. Depth-related effects and want to receive shadow as well. I want to know your implementation in more detail, can I?
     
  12. liyongunity

    liyongunity

    Joined:
    May 15, 2015
    Posts:
    3
    Just achieved similar effect. there are the steps to follow:
    1. wirte a shader of your own. there are two ways:
      1. a surface shader with
        Code (CSharp):
        1. ZWrite Off
        , or:
      2. a custom vert/frag shader, with two passes. Write colors with the first pass, with ZWrite set to Off. the second pass should be a ShadowCaster, which means
        Code (JavaScript):
        1. "LightMode" = "ShadowCaster"
        , and most important of all, this is a must:
        Code (CSharp):
        1. ZWrite On
    2. setup your renderer on the inspector panel, or in your code, just like this
      Code (CSharp):
      1. Renderer.shadowCastingMode = ShadowCastingMode.Off
    3. turn on shadow receving:
      Code (CSharp):
      1. Renderer.receiveShadows = true
    then you'll get a object with only shadow receiving.