To read the depth value at the current pixel from on-chip memory in a custom Universal Render Pipeline (URP) shader, use the FetchSceneDepth method with a depth input attachment.
A render pass can read the depth buffer that a previous render pass wrote to on-chip memory, instead of writing it to a separate texture and sampling it back. In some graphics APIs, this is called an input attachment. For more information, refer to Get the current framebuffer from GPU memory in URP.
Reading depth using an input attachment reduces memory bandwidth in your project, especially on mobile devices that use tile-based deferred rendering (TBDR). This is useful to optimize effects like custom on-tile depth-based fog and depth-based smoke.
Note: If you use Shader Graph, use the Fetch Scene Depth node instead of writing HLSL.
Depth input attachments are supported only in URP, and only on DirectX 12 and Vulkan. You can check whether your target platform supports this at runtime by using SystemInfo.supportsDepthAttachmentAsInputAttachment.
To read depth from on-chip memory in your shader, do the following:
In your URP Renderer, add a Render Objects Renderer Feature.
In the Render Objects Renderer Feature, enable Depth and Set As Input Attachment.
In the Render Objects Renderer Feature, set Event to After Rendering Opaques or later. This is necessary because depth data is available only after the opaque pass.
In the HLSLPROGRAM in your shader file, add an #include directive for the DeclareDepthTexture.hlsl file.
For example:
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
Add a multi_compile pragma for the input attachment keywords, so Unity can enable the correct keyword for the pass. URP sets these keywords automatically at runtime when a render pass uses depth as an input attachment.
For example:
// URP sets these keywords automatically at runtime; you don't set them manually.
#pragma multi_compile _ _DEPTH_AS_INPUT_ATTACHMENT _DEPTH_AS_INPUT_ATTACHMENT_MSAA
In your fragment shader, call FetchSceneDepth to read the depth value at the current pixel. For more information about the overloads, refer to FetchSceneDepth overloads.
For example:
#if defined(_DEPTH_AS_INPUT_ATTACHMENT)
// Reads depth directly from on-chip memory instead of sampling a depth texture.
float depth = FetchSceneDepth(input.positionCS.xy);
#elif defined(_DEPTH_AS_INPUT_ATTACHMENT_MSAA)
float depth = FetchSceneDepth(input.positionCS.xy, 0);
#endif
Create a material from your shader, and assign it to objects that your Render Objects Renderer Feature affects.
The material now reads current depth values from on-chip memory instead of a separate depth texture sample.
FetchSceneDepth has the following overloads.
| Syntax | Description |
|---|---|
float FetchSceneDepth(float2 fragCoord) |
Returns the depth at the current pixel. Use this overload when the _DEPTH_AS_INPUT_ATTACHMENT keyword is defined. |
float FetchSceneDepth(float2 fragCoord, int sampleIndex) |
Returns the depth at the current pixel for the given MSAA sample index. Use this overload when the _DEPTH_AS_INPUT_ATTACHMENT_MSAA keyword is defined. |
The following example shader code reads the current pixel depth and writes it to the red channel of the output color.
Shader "CustomFetchDepth"
{
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}
Pass
{
Name "CustomFetchDepth"
ZWrite On
ZTest LEqual
Cull Off
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment FragDepthVisualization
#pragma target 4.5
// URP sets these keywords automatically at runtime; you don't set them manually.
#pragma multi_compile _ _DEPTH_AS_INPUT_ATTACHMENT _DEPTH_AS_INPUT_ATTACHMENT_MSAA
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionCS : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
Varyings Vert(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
return output;
}
#if defined(_DEPTH_AS_INPUT_ATTACHMENT)
float4 FragDepthVisualization(Varyings input) : SV_Target0
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
// Reads depth directly from on-chip memory instead of sampling a depth texture.
float depth = FetchSceneDepth(input.positionCS.xy);
return float4(depth, 0.0f, 0.0f, 1.0);
}
#elif defined(_DEPTH_AS_INPUT_ATTACHMENT_MSAA)
float4 FragDepthVisualization(Varyings input) : SV_Target0
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
// Reads depth directly from on-chip memory for the given MSAA sample, instead of sampling a depth texture.
float depth = FetchSceneDepth(input.positionCS.xy, 0);
return float4(depth, 0.0f, 0.0f, 1.0);
}
#else
float4 FragDepthVisualization(Varyings input) : SV_Target0
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
// Unity doesn't provide an automatic fallback to depth texture sampling here.
float depth = 0;
return float4(depth, 0.0f, 0.0f, 1.0);
}
#endif
ENDHLSL
}
}
}