Search Unity

Creating a Shadow Caster Pass for a Ray Marching Shader

Discussion in 'Shaders' started by trastopchin, May 7, 2021.

  1. trastopchin

    trastopchin

    Joined:
    Apr 17, 2019
    Posts:
    2
    Hi everyone! I'm working on a shader that renders implicit surfaces using ray marching and I wanted to ask if anyone has advice or direction with how to go about writing the shadow pass. I have been following along with Catlike Coding's rendering tutorial (https://catlikecoding.com/unity/tutorials/rendering/part-7/), and from what I understand Unity relies on depth textures to determine if something is shadowed. With this understanding, I thought that if I specify to the shadow caster the correct z-depth per fragment, I would be able to get the rendered implicit surface to cast shadows. However, even if that's possible, I was not able to implement it correctly.

    I render implicit surfaces using quad meshes as "portals" where the world position corresponding to each fragment is used as the eye of a ray for ray marching an implicit surface. All my lighting vertex shader does is write out the clip space vertex position and pass along the corresponding world space vertex position and ray origin (the world space camera position).

    Code (HLSL):
    1. // Vertex data -> vertex shader
    2. struct VertexData {
    3.     float4 position: POSITION;
    4. };
    5.  
    6. // Vertex shader -> interpolators -> fragment shader
    7. struct Interpolators {
    8.     float4 clipPos : SV_POSITION;
    9.     float4 worldPos : TEXCOORD0;
    10.     float3 rayOrigin : TEXCOORD1;
    11. };
    12.  
    13. // Vertex data -> vertex shader -> interpolators
    14. Interpolators vertexProgram (VertexData v)
    15. {
    16.     Interpolators i;
    17.     i.clipPos = UnityObjectToClipPos(v.position);
    18.     i.worldPos = mul(unity_ObjectToWorld, v.position);
    19.     i.rayOrigin = _WorldSpaceCameraPos;
    20.     return i;
    21. }
    My lighting fragment shader then uses the world position and ray origin to perform the ray march, compute the illumination, and write out the z-depth.

    Code (HLSL):
    1. // Write to the color and depth buffers
    2. struct FragmentData {
    3.     fixed4 color : SV_TARGET;
    4.     float depth : SV_DEPTH;
    5. };
    6.  
    7. // Fragment shader
    8. FragmentData fragmentProgram (Interpolators i)
    9. {
    10.     float3 worldSpacePoint = rayMarch(i.worldPos, i.rayOrigin);
    11.     if (rayMarchHit(worldSpacePoint)) {
    12.         float3 worldSpaceNormal = implicitSurfaceNormal(point);
    13.  
    14.         // Compute illumination
    15.         output.color.rgb = computeIllumination(worldSpacePoint, worldSpaceNormal);
    16.  
    17.         // Write z depth
    18.         float4 objectSpacePoint = mul(unity_WorldToObject, float4(worldSpacePoint, 1));
    19.         float4 clipSpacePoint = UnityObjectToClipPos(objectPosSurfacePoint);
    20.         output.depth = clipPosSurfacePoint.z / clipPosSurfacePoint.w;
    21.         return output;
    22.     }
    23.     else {
    24.         discard;
    25.     }
    26. }
    Is there any way that I could use the resulting worldSpacePoint or output.depth values in order to write a shadow caster that properly casts shadows for the rendered implicit surface?

    In Catlike Coding's directional light example, it seems like the GPU was able to correctly infer the fragment depth from the SV_POSITION semantic; all the fragment shader did was return 0. I can't use the SV_POSITION semantic because I need to perform the ray march per-fragment in the fragment shader (as opposed to per-vertex in the vertex shader). When I tried to copy my lighting vertex and fragment programs and paste them into a shadow caster pass, the shadows acted as they ignored the SV_DEPTH semantic that I set. That is, they shadows were casted as if I was just rendering the flat quad with the region that does not hit the implicit surface during the ray march cut out.

    However, when Catlike Coding implements point light shadows, it looks like the fragment program meaningfully outputs depth information that is used to properly cast a shadow:

    Code (HLSL):
    1. struct Interpolators {
    2.     float4 position : SV_POSITION;
    3.     float3 lightVec : TEXCOORD0;
    4. };
    5.  
    6.  Interpolators MyShadowVertexProgram (VertexData v) {
    7.     Interpolators i;
    8.     i.position = UnityObjectToClipPos(v.position);
    9.     i.lightVec =
    10.         mul(unity_ObjectToWorld, v.position).xyz - _LightPositionRange.xyz;
    11.     return i;
    12. }
    13.  
    14. float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
    15.     float depth = length(i.lightVec) + unity_LightShadowBias.x;
    16.     depth *= _LightPositionRange.w;
    17.     return UnityEncodeCubeShadowDepth(depth);
    18. }
    It seems like in the case of the directional light Unity uses the SV_POSITION semantic to compute the shadow caster depth pass but in the case of the point light we can somehow provide additional context to / possibly override this value in the fragment shader.

    To summarize, my two big questions are:
    1. Is there any way that in the shadow caster pass I can specify the depth the GPU uses to render the shadows? Can I do this in the fragment shader, bypassing the SV_POSITION semantic?
    2. Is there perhaps a different way that I could approach writing a shadow caster pass for a shader that uses ray marching to render implicit surfaces?

    Thank you so much!
     
    Last edited: May 7, 2021
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yes.
    SV_Depth
    .

    See the shadow casting & receiving sections of this article:
    https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c
     
    trastopchin likes this.
  3. trastopchin

    trastopchin

    Joined:
    Apr 17, 2019
    Posts:
    2