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

Question CommandBuffer, DrawMeshInstanced, & ShadowCasting

Discussion in 'Universal Render Pipeline' started by Canijo, Oct 1, 2022.

  1. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    47
    So i dont manage to find any example on how to draw to the ShadowMap while doing a DrawMeshInstanced with CommandBuffers inside a CustomPass

    All of the "Graphics.DrawMesh..." have overloads specifiyng the ShadowCastingMode & receive shadows, but they dont exist for the CommandBuffer methods. I am guessing this is because we need to do it manually but im confused about how to move forward.

    Currently, existing shadows (like the ones casted from a regular MeshRenderer) are being received, but we are not casting any.

    I guess i should get a reference to the shadowMap in some moment, then render with CommandBuffer.DrawMeshInstanced specifiying the ShadowCaster pass, but for each light affecting each object? how should i go about that?. can we figure out this together :)?
     
  2. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    761
    Hi, have you tried setting the shaderPass below to -1?
    Code (CSharp):
    1. DrawMeshInstanced(Mesh mesh, int submeshIndex, Material material, int shaderPass, Matrix4x4[] matrices, int count);
    2.  
    3. //   shaderPass:
    4. //     Which pass of the shader to use, or -1 which renders all passes.

    If you need to set the render target to shadowmaps and enqueue a custom ShadowCaster pass.
    You can use:
    Code (CSharp):
    1. ScriptableRenderPass.ConfigureTarget();
    2.  
    3. // URP does not suggest using this.
    4. CommandBuffer.SetRenderTarget();
    For directional light shadow, the shadowmap is called "_MainLightShadowmapTexture".

    See "MainLightShadowCasterPass.cs" line 136 (URP 15):
    URP_MainLightShadowmapTexture.png

    URP 12 (and lower) uses "RenderTextureIdentifier" instead of "RTHandle", so you can set the render target directly by providing RenderTexture's name. (If it exists)
    Code (CSharp):
    1. ScriptableRenderPass.ConfigureTarget("_MainLightShadowmapTexture")
    In URP 13+, I think you'll have to get this shadowmap RTHandle from URP's internal class.
     
  3. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    47
    Yes, -1 should render all passes, but this is not what i want (still, no shadows are drawn).

    This is what i understand i should do: (but aint working)

    I need a feature with 2 pases, one for UniversalForward, and 1 for ShadowCaster pass.

    For and opaque pass, the renderEvt for the UniversalForward pass should be "After Rendering Opaques", and for the shadowCaster pass it should be "After Rendering Shadows" (im also trying before in both).

    The universal pass renders with DrawMeshInstanced with 0 as pass index (or .FindPass("ForwardLit"))
    The shadowPass renders with DrawMeshInstanced, previously having set the "_MainLightShadowmapTexture" as the target with ConfigureTarget().

    Feature

    Code (CSharp):
    1.  
    2.          
    3. public class GrassRenderFeature : ScriptableRendererFeature
    4. {
    5.     [SerializeField]
    6.     private GrassSettings _settings;
    7.  
    8.     GrassRenderPass _grassPass;
    9.     GrassShadowPass _shadowPass;
    10.  
    11.     public override void Create()
    12.     {
    13.         GrassPositions positions = new GrassPositions(_settings);
    14.         _grassPass = new GrassRenderPass(_settings, positions);
    15.         _shadowPass = new GrassShadowPass(_settings, positions);
    16.  
    17.         _grassPass.renderPassEvent = _settings.renderPassEvt;
    18.         _shadowPass.renderPassEvent = _settings.shadowPassEvt;
    19.     }
    20.  
    21.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    22.     {
    23.         renderer.EnqueuePass(_grassPass);
    24.         renderer.EnqueuePass(_shadowPass);
    25.     }
    26.  
    27.  
    ForwardLit Pass

    Code (CSharp):
    1.  
    2.     class GrassRenderPass : ScriptableRenderPass
    3.     {
    4.         private GrassSettings _settings;
    5.         private GrassPositions _positions;
    6.  
    7.         public GrassRenderPass(GrassSettings settings, GrassPositions positions)
    8.         {
    9.             _settings = settings;
    10.             _positions = positions;
    11.         }
    12.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    13.         {
    14.         }
    15.  
    16.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    17.         {
    18.             if (!_settings.IsValid())
    19.                 return;
    20.             MaterialPropertyBlock block = new MaterialPropertyBlock();
    21.             CommandBuffer cmd = CommandBufferPool.Get();
    22.             cmd.name = "ForwardLit (instanced)";
    23.             cmd.DrawMeshInstanced(_settings.mesh, 0, _settings.material, _settings.material.FindPass("ForwardLit"), _positions.array, _positions.length, block);
    24.             context.ExecuteCommandBuffer(cmd);
    25.             CommandBufferPool.Release(cmd);
    26.         }
    27.  
    28.  
    29.         public override void OnCameraCleanup(CommandBuffer cmd)
    30.         {
    31.         }
    32.     }
    ShadowPass

    Code (CSharp):
    1.  
    2.     class GrassShadowPass : ScriptableRenderPass
    3.     {
    4.         private GrassSettings _settings;
    5.         private GrassPositions _positions;
    6.  
    7.         public GrassShadowPass(GrassSettings settings, GrassPositions positions)
    8.         {
    9.             _settings = settings;
    10.             _positions = positions;
    11.         }
    12.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    13.         {
    14.         }
    15.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    16.         {
    17.             if (!_settings.IsValid())
    18.                 return;
    19.             this.ConfigureTarget("_MainLightShadowmapTexture");
    20.             MaterialPropertyBlock block = new MaterialPropertyBlock();
    21.             CommandBuffer cmd = CommandBufferPool.Get();
    22.             cmd.name = "ShadowCaster (intanced)";
    23.             cmd.DrawMeshInstanced(_settings.mesh, 0, _settings.material, _settings.material.FindPass("ShadowCaster"), _positions.array, _positions.length, block);
    24.             context.ExecuteCommandBuffer(cmd);
    25.             CommandBufferPool.Release(cmd); ;
    26.         }
    27.  
    28.         public override void OnCameraCleanup(CommandBuffer cmd)
    29.         {
    30.         }
    31.     }
    It aint working (grass renders, and receives lights & shadows, but doesnt cast shadows) , but it gets me thinking that REALLY what i want to do is render into the shadowmap from each light? do i have to manually render each light into the shadowMap?, i see there is a .AddCommandBuffer() method on to the "Light" class, is this the right direction?

    Code (CSharp):
    1.  
    2.         //
    3.         // Summary:
    4.         //     Add a command buffer to be executed at a specified place.
    5.         //
    6.         // Parameters:
    7.         //   evt:
    8.         //     When to execute the command buffer during rendering.
    9.         //
    10.         //   buffer:
    11.         //     The buffer to execute.
    12.         //
    13.         //   shadowPassMask:
    14.         //     A mask specifying which shadow passes to execute the buffer for.
    15.         public void AddCommandBuffer(LightEvent evt, CommandBuffer buffer);
     
  4. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    761
    Edited: Camera.AddCommandBuffer() and Light.AddCommandBuffer() are both not supported.

    Seems that URP doesn't support it.
    AddCMD_URP.png

    Light_AddCMD.png

    What version's URP are you using? URP 12/+?
     
    Last edited: Oct 1, 2022
  5. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    761
    Hi, I've found something.

    Seems that DrawMeshInstanced also lacks support for MotionVectors.

    They suggest using a new function called RenderMeshIndirect. (Mentioned in this thread)
     
  6. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    47
    :) im going to try this and tell you how it goes, seems that i have to dig a bit
     
    wwWwwwW1 likes this.
  7. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    47
    I never came back here to post what i found out, so in case someone is interested:

    To render the Shadows i needed to provided a way to pass my own RenderPass to render using the ShadowCaster pass while unity is rendering its shadows.

    Rendering shadows seems to be just render the objects to the propper RT's while using projection matrices that come from the Light's position instead of the camera.

    Within the UniversalRenderer.cs, unity enqeues the "MainLightShadowCasterPass" and "AdditionalLightsShadowCasterPass". All the calculations for rendering are done inside them, and you need to render right after unity renders, and before it clears the necessary data.

    Modifying the code, It's easy to provide a way to execute your own commands passing down the prepared commandBuffer in the right moment for both passes. I would just LOVE unity to provide a builtin-way to do it, as for building plugins/packages we cannot rely on users overriding the URP, or us keeping up with the changes on a modified version. And i think there is NO way to actually cast shadows in custom passes when doing custom rendering (avoiding GO's or "Renderer" Components)

    Its nice to know that it could be done though =D.
     
    pepe_q, Nyapsora and wwWwwwW1 like this.
  8. pepe_q

    pepe_q

    Joined:
    Dec 1, 2016
    Posts:
    14
    Hi Canijo!
    Sorry did you mean on your last post it's actually not possible to do a shadow pass on command buffer without changing URP code? I'm precisely trying to convert Graphics.RenderMeshIndirect() into command buffers and having the same problem about shadow casting (and actually the same on the layer setting)
    Thanks!
    (Por cierto parece que eres paisano de Madrid? saludos desde Kioto ;)
     
    Last edited: Feb 2, 2024
  9. Canijo

    Canijo

    Joined:
    Oct 9, 2018
    Posts:
    47
    It's been a while since i messed with rendering code, but from what i remember, i could not find a way to do it without modifying code.
    Im eyeballing the URP code now (on 2023.2.3f1) and i see that there has been some changes, but i believe that the constraints are the same and there might still not be a builtin way to achieve it.

    The problem is that there are many keywords and data that needs to be setup before rendering a ShadowCaster Pass onto anything, and many settings that drive how shadows are actually rendered, like cascading, or softshadows, and depending on those, shadows might be drawn multiple times at different resolutions in different parts of the same texture. Also additional lights are rendered on a different pass than the MainLight shadows. .

    The easiest way for me to replicate that behaviour without going all crazy and reimplement everything, was to directly provide a way to expose the command buffer that is being used on those passes right before all that data was "Cleared". So i created a custom base class for Passes that want to render into Shadows, with a method that accepts the command buffer and the relevant data that i could need from the pass. At the setup stage, i stored those passes into the Main and Additional shadow passes, so i could call them from there each time they render a "ShadowSlice".

    The relevant URP classes are
    MainLightShadowCasterPass
    and
    AdditionalLightsShadowCasterPass
    ,

    On the MainLight pass the rendering happens here, and on the additionalLightsShadow pass is quite similar
    Code (CSharp):
    1. namespace UnityEngine.Rendering.Universal.Internal
    2. {
    3.     public class MainLightShadowCasterPass : ScriptableRenderPass
    4.     {
    5. /// ....
    6.       void RenderMainLightCascadeShadowmap(RasterCommandBuffer cmd, ref PassData data, ref RenderingData renderingData, bool isRenderGraph)
    7.         {
    8.          /// ....
    9.  
    10.                 for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex)
    11.                 {
    12.                      /// ....
    13.                      /// Called once per cascade, with different View & Proj matrices
    14.                     ShadowUtils.RenderShadowSlice(cmd, ref m_CascadeSlices[cascadeIndex], ref shadowRendererList, m_CascadeSlices[cascadeIndex].projectionMatrix, m_CascadeSlices[cascadeIndex].viewMatrix);
    15.                 }
    16.         }
    17. }
    18.  
    Also, check the
    ShadowUtils.RenderShadowSlice(...)
    method, as it is there where the "Viewport" (part of the texture) and Proj&View matrices are set. And that very method "Renders" and then calls "DisableScissorRect" and also resets the depth bias values. You might need to modify it so you can render before it does that, so you can manually do it after
    PD: Sí ^^, pero yo te saludo desde Canarias, que es donde vivo ahora :p
     
    Last edited: Feb 2, 2024
    pepe_q likes this.
  10. pepe_q

    pepe_q

    Joined:
    Dec 1, 2016
    Posts:
    14
    Ok I see, so you actually get your command buffer injected into all needed shadow passes :)
    I'm taking note of it so I give it a try a bit later, thank you so much!
     
    Canijo likes this.