Unity 6 URP How to access camera depth texture?

Hello,

I need to obtain the main camera’s depth texture but since global textures are getting reset in the newer versions of URP, I cannot get it via Shader.GetGlobalTexture("_CameraDepthTexture"), it returns 1x1 black texture. I tried getting it in SRP’s endCameraRendering event, still same. I inspected RenderGraph samples, I can obtain the depth texture in ScriptableRendererFeature, but I couldn’t figure out how to save and use that texture in C#. To be clear, I am trying to add occlusion culling for my GPU instancing solution, which dispatches a compute shader, and for hiZ-depth, I first need to get camera depth texture, current frame or previous, does not matter that much for me. I am still new to these topics so if anyone can help me, that would be awesome.

Thanks in advance!

You can get access to the cameraDepthTexture through the frame data (see the overview of the resources).

You can find an overview of the RenderGraph learning resources here.

The RenderGraph package samples indeed shows you how to access the frame data and how to use the texture in your render pass.

If you need to use the texture beyond the current camera render, you’ll need to set up a RTHandle (a persistent texture) and copy the depth to that handle to store it for a next frame or camera.

Thanks for the help!

I have tried different things from the samples and the ChatGPT, to wrap my head around it and I finally made it to work. I am not sure if it is the optimal/correct way but it works.

I will leave the code here for anyone who have a similar scenario (save camera depth texture to a persistent texture for later use in that frame or the next frames) and can’t figure out how to do it.

Also, any suggestions to improve are welcome.

using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

namespace GrayWolf.GPUInstancing.Domain
{
    public class PersistentDepthFeature : ScriptableRendererFeature
    {
        /// <summary>
        /// Returns the last cached camera depth texture. (It can be this frame or one of previous frames)
        /// </summary>
        public static RTHandle PersistentDepthTexture => _persistentDepthTexture;

        private DepthCopyPass _depthPass;
        private static RTHandle _persistentDepthTexture;
        private static readonly int k_PersistentCameraDepthID = Shader.PropertyToID("_PersistentCameraDepth");

        [SerializeField] private RenderPassEvent renderPassEvent;

        public override void Create()
        {
            _depthPass = new DepthCopyPass(renderPassEvent);
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if(renderingData.cameraData.cameraType != CameraType.Game)
                return;
        
            // Ensure our persistent depth RT is allocated with current camera size
            var desc = renderingData.cameraData.cameraTargetDescriptor;
            desc.depthBufferBits = 0;                           // no depth buffer (we're storing depth as color)
            desc.msaaSamples = 1;                               // no MSAA for the depth texture
            desc.stencilFormat = GraphicsFormat.None;           // no stencil
            desc.graphicsFormat = GraphicsFormat.R32_SFloat;    // 32-bit float single channel
        
            RenderingUtils.ReAllocateHandleIfNeeded(ref _persistentDepthTexture, desc, FilterMode.Point, TextureWrapMode.Clamp, name: "_PersistentDepthTexture");
                
            if (PersistentDepthTexture == null || PersistentDepthTexture.rt == null)
            {
                Debug.LogError("Persistent Depth RT is null.");
            }
        
            // Assign the RTHandle to our pass so it can import it
            _depthPass.Setup(PersistentDepthTexture);
        
            renderer.EnqueuePass(_depthPass);
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (_persistentDepthTexture != null)
            {
                _persistentDepthTexture.Release();
                _persistentDepthTexture = null;
            }
        }
    
        class DepthCopyPass : ScriptableRenderPass
        {
            private RTHandle _persistentDepthHandle;

            public DepthCopyPass(RenderPassEvent renderPassEvent)
            {
                this.renderPassEvent = renderPassEvent;
                ConfigureInput(ScriptableRenderPassInput.Depth);
            }

            public void Setup(RTHandle persistentDepthHandle)
            {
                this._persistentDepthHandle = persistentDepthHandle;
            }

            // RecordRenderGraph is called each frame to build the render graph pass
            public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
            {
                // Import the persistent RTHandle into the render graph (for writing)
                TextureHandle depthTarget = renderGraph.ImportTexture(_persistentDepthHandle);
                // Get the current camera depth (frame data) texture handle
                UniversalResourceData frameData = frameContext.Get<UniversalResourceData>();
                TextureHandle cameraDepth = frameData.cameraDepthTexture;
            
                if (!cameraDepth.IsValid())
                {
                    Debug.LogError("Camera depth texture is not valid!");
                    return;
                }
            
                renderGraph.AddBlitPass(cameraDepth, depthTarget, Vector2.one, Vector2.zero, passName: "Copy Depth To Persistent Texture");
                Shader.SetGlobalTexture(k_PersistentCameraDepthID, _persistentDepthTexture.rt);
            }
        }
    }
}