Occlude / Stencil cut parts of objects

Question: How to perform a cutoff / intersect / occlude / stencil cut of some parts of objects (that are not “visible” due to being rendered over by other objects)?

I am trying to write normal information for a set of objects (that belong to a specific layer) into a texture, that I can then use for future post processing calculations. This is done using a ScriptableRenderPass.

The code looks as such:

filteringSettings = new FilteringSettings(RenderQueueRange.opaque, layerMask);

...

DrawingSettings drawSettings = CreateDrawingSettings(shaderTagsToRender, ref renderingData, SortingCriteria.BackToFront);
drawSettings.overrideMaterial = normalsMaterial;
drawSettings.perObjectData = PerObjectData.None;

CoreUtils.SetRenderTarget(cmd, normalsTextureRef, normalsTextureRef);
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filteringSettings);

This will produces this (while I am only interested in the part “above ground”):

This is a simplified example. There will be many ground objects etc.
It feels like this should be a common scenario and that the framework should support these kind of things (even if it means rendering the entire screen again).

Any guidance? What would be your approach?

For example, could I somehow perform a ZTest in my shader graph, comparing the vertex positions with the camera depth texture and simply color them black?

I don’t know much about render passes, but I’ve done this often with a render feature:

It doesn’t integrate well with shader graph from my experience tho. Maybe 2022 or 2023 does support it, haven’t tested there

Hi, you can override the depth test (to ZTest Equal) settings in your custom renderer feature.

Also, you need to set the depth render target to camera’s depth buffer and its render pass event to after opaque/transparent rendering.

Example for URP 12 to override depth test:

private RenderStateBlock renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);

public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
    //...
    // "ZWrite Off" & "ZTest Equal".
    renderStateBlock.depthState = new DepthState(false, CompareFunction.Equal);
    // Tell URP that we changed depth render state.
    renderStateBlock.mask |= RenderStateMask.Depth;
    //...
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    //...
    // You can also use ConfigureTarget(color, depth) in OnCameraSetup().
    // Change depth target to "cameraDepthTargetHandle" if URP needs a RTHandle.
    CoreUtils.SetRenderTarget(cmd, normalsTextureRef, renderingData.cameraData.renderer.cameraDepthTarget);
    // Pass the RenderStateBlock to DrawRenderers.
    context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings, ref renderStateBlock);
    //...
}

Thanks for the reply. I glanced at that video before, but doesn’t feel like a render feature will solve it for me, as I need to write this to a texture.

Thanks a lot! I tried using the RenderStateBlock before in my attempts, but never passed a long the cameraDepthTargetHandle for my render target.

In addition to this, the normal DrawRenderers simply didn’t work for me.
I reverse engineered the Render Feature functionality and used the RenderingUtils.DrawRendererListWithRenderStateBlock that Unity use internally instead (source). This worked.

Well, celebrated a bit too early.
Turns out I get the reverse problem instead when I have a large object that I need the normals for, while some smaller object happens to be on top of it. From what I can tell, my effect is simply not worth doing in post processing.