Failed to Create a Custom Render Pass in HDRP: MRT Not Working

Version: HDRP 17.0.3, Unity 6000.0.29f1

I prefer modifying the HDRP source code rather than using CustomPass because the CustomPass examples I’ve seen so far are mostly focused on post-processing, whereas I aim to work on GI-related tasks.

My goal is to add a render pass in the HDRP source code to capture an RSMBuffer from the light’s perspective. However, for testing purposes, I output red, yellow, and blue directly in the fragment shader to check if the shader works. Frustratingly, the multi-target rendering does not produce the expected output.

The RenderGraph Viewer shows that I have this pass, but it doesn’t display any resources.


Additionally, the Frame Debugger doesn’t show the expected textures.

I referred to the implementation of RenderForwardOpaque. Below are the steps I followed for my implementation. Thank you all for your help!
(1) In the HDRenderPipeline.RenderGraph.cs file, right after RenderShadows process, I called the RenderRSMGIBuffer function,

which is defined as follows:

void RenderRSMGIBuffer(RenderGraph renderGraph,
            HDCamera hdCamera,
            TextureHandle colorBuffer,
            in LightingBuffers lightingBuffers,
            in BuildGPULightListOutput lightLists,
            RSMGIBuffers rendergraph_rsmgiBuffers,
            TextureHandle vtFeedbackBuffer,
            ShadowResult shadowResult,
            CullingResults cullResults)
        {
            bool debugDisplay = m_CurrentDebugDisplaySettings.IsDebugDisplayEnabled();

            using (var builder = renderGraph.AddRenderPass<RSMGIPassData>(debugDisplay ? "RSM Forward (+ Emissive) Opaque  Debug" : "RSM Forward (+ Emissive) Opaque ",
                out var passData,
                debugDisplay ? ProfilingSampler.Get(HDProfileId.RSMDebug) : ProfilingSampler.Get(HDProfileId.RSM)))
            {
                var rendererList = renderGraph.CreateRendererList(RSMBufferRendererList(cullResults, hdCamera));
                builder.UseRendererList(rendererList);
            
                int index = 0;
                passData.rsmGIPassData_rsmgiBuffers.rsmPositionBuffer = builder.UseColorBuffer(rendergraph_rsmgiBuffers.rsmPositionBuffer, index++);
                passData.rsmGIPassData_rsmgiBuffers.rsmNormalBuffer = builder.UseColorBuffer(rendergraph_rsmgiBuffers.rsmNormalBuffer, index++);
                passData.rsmGIPassData_rsmgiBuffers.rsmFluxBuffer= builder.UseColorBuffer(rendergraph_rsmgiBuffers.rsmFluxBuffer, index++);
                passData.noUseDepthBuffer = builder.UseDepthBuffer(CreateDepthBuffer(renderGraph, true, MSAASamples.None), DepthAccess.Write);

                builder.AllowRendererListCulling(false);
                
                builder.SetRenderFunc(
                    (RSMGIPassData data, RenderGraphContext context) =>
                    {
                        BindGlobalRSMPassBuffers(data, context.cmd);

                        CoreUtils.DrawRendererList(context.renderContext, context.cmd, rendererList);
DecalSystem.instance.RenderForwardEmissive(context.cmd);
                    });
            }
        }

In the Lit.shader, I provided the Pass RSMPass:

Pass
        {
            Name "MyRSMPass"
            Tags{ "LightMode" = "RSMPass" }

            Cull[_CullMode]

            ZClip [_ZClip]
            ZWrite On
            ZTest LEqual

            ColorMask 0

            HLSLPROGRAM

            #pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
            //enable GPU instancing support
            #pragma multi_compile_instancing
            #pragma instancing_options renderinglayer
            #pragma multi_compile _ DOTS_INSTANCING_ON
            // enable dithering LOD crossfade
            #pragma multi_compile _ LOD_FADE_CROSSFADE

            #pragma shader_feature_local _ALPHATEST_ON

            #define SHADERPASS SHADERPASS_RSM
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/Lit.hlsl"
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/ShaderPass/LitDepthPass.hlsl"
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/LitData.hlsl"
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassRSMBuffer.hlsl"

            #pragma vertex Vert
            #pragma fragment Frag

            ENDHLSL
        }

My ShaderPassRSMBuffer.hlsl content is as follows:

#if (SHADERPASS != SHADERPASS_RSM)
#error SHADERPASS_is_not_correctly_define
#endif
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/VertMesh.hlsl"

PackedVaryingsType Vert(AttributesMesh inputMesh)
{
    VaryingsType varyingsType;
    varyingsType.vmesh = VertMesh(inputMesh);
    return PackVaryingsType(varyingsType);
}

void Frag(  PackedVaryingsToPS packedInput, 
	out float4 outPositionWS : SV_Target0, 
	out float4 outNormalWS : SV_Target1,
	out float4 outColor : SV_Target2)
{
	outPositionWS = float4(1.0, 0.0, 0.0, 1.0); //red
	outNormalWS = float4(0.0, 1.0, 0.0, 1.0);  // green
	outColor = float4(0.0, 0.0, 1.0, 1.0);     // blue
}

(2) Here are some additional details: the definitions of RSMGIPassData and RSMGIBuffers are as follows:

        class RSMGIPassData
        {
            public RSMGIBuffers rsmGIPassData_rsmgiBuffers;
            public TextureHandle noUseDepthBuffer;
        }
        struct RSMGIBuffers
        {
            public TextureHandle rsmPositionBuffer;
            public TextureHandle rsmNormalBuffer;
            public TextureHandle rsmFluxBuffer;
        }

And initialized as follows:

RSMGIBuffers deferred_rsmgiBuffers = new RSMGIBuffers();
                deferred_rsmgiBuffers.rsmPositionBuffer = CreateRSMPositionBuffer(m_RenderGraph, hdCamera, false, "defer_RSMPosition");
                deferred_rsmgiBuffers.rsmNormalBuffer = CreateRSMNormalBuffer(m_RenderGraph, hdCamera, hdCamera.msaaSamples,"defer_RSMNormal");
                deferred_rsmgiBuffers.rsmFluxBuffer = CreateRSMFluxBuffer(m_RenderGraph, hdCamera, false,"defer_RSMFlux");

TextureHandle CreateRSMPositionBuffer(RenderGraph renderGraph, HDCamera hdCamera, bool msaa, string name)
        {
            return renderGraph.CreateTexture(
                new TextureDesc(Vector2.one, true, true)
                {
                    // format = GetColorBufferFormat(),
                    format = GraphicsFormat.R32G32B32A32_SFloat,
                    enableRandomWrite = !msaa,
                    bindTextureMS = msaa,
                    msaaSamples = msaa ? hdCamera.msaaSamples : MSAASamples.None,
                    // clearBuffer = NeedClearColorBuffer(hdCamera),
                    clearBuffer = true,
                    clearColor = GetColorBufferClearColor(hdCamera),
                    name = msaa ? name+"MSAA" : name
                });
        }

 TextureHandle CreateRSMNormalBuffer(RenderGraph renderGraph, HDCamera hdCamera, MSAASamples msaaSamples, string name)
        {
            bool msaa = msaaSamples != MSAASamples.None;
            TextureDesc normalDesc = new TextureDesc(Vector2.one, true, true)
            {
                // format = GraphicsFormat.R8G8B8A8_UNorm,
                format = GraphicsFormat.R32G32B32A32_SFloat,
                // clearBuffer = NeedClearGBuffer(hdCamera),
                clearBuffer = true,
                clearColor = Color.black,
                bindTextureMS = msaa,
                msaaSamples = msaaSamples,
                enableRandomWrite = !msaa,
                name = msaa ? name+"MSAA" : name,
                fallBackToBlackTexture = true,
            };
            return renderGraph.CreateTexture(normalDesc);
        }

 TextureHandle CreateRSMFluxBuffer(RenderGraph renderGraph, HDCamera hdCamera, bool msaa,  string name)
        {
            return renderGraph.CreateTexture(
                new TextureDesc(Vector2.one, true, true)
                {
                    // format = GetColorBufferFormat(),
                    format = GraphicsFormat.R32G32B32A32_SFloat,
                    enableRandomWrite = !msaa,
                    bindTextureMS = msaa,
                    msaaSamples = msaa ? hdCamera.msaaSamples : MSAASamples.None,
                    // clearBuffer = NeedClearColorBuffer(hdCamera),
                    clearBuffer = true,
                    clearColor = GetColorBufferClearColor(hdCamera),
                    name = msaa ? name+"MSAA" : name
                });
        }


 TextureHandle CreateDiffuseLightingBuffer(RenderGraph renderGraph, MSAASamples msaaSamples)
        {
            bool msaa = msaaSamples != MSAASamples.None;
            return renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true)
            {
                format = GraphicsFormat.B10G11R11_UFloatPack32,
                enableRandomWrite = !msaa,
                bindTextureMS = msaa,
                msaaSamples = msaaSamples,
                clearBuffer = true,
                clearColor = Color.clear,
                name = msaa ? "CameraSSSDiffuseLightingMSAA" : "CameraSSSDiffuseLighting"
            });
        }

(3)The rendererList I used is defined as follows:

        RendererListDesc RSMBufferRendererList(CullingResults cullResults, HDCamera hdCamera)
        {
            var passName = m_RSMPassNames[0];
            return CreateOpaqueRendererListDesc(cullResults, hdCamera.camera, passName, m_CurrentRendererConfigurationBakedLighting);
        }

(4)In the HDRenderPipeline.cs file, I also wrote the corresponding ShaderTagID list:

ShaderTagId[] m_RSMPassNames = { HDShaderPassNames.s_RSMName};

And in the HDStringConstants.cs file,

public static readonly string s_RSMPass_Str = "RSMPass";
public static readonly ShaderTagId s_RSMName = new ShaderTagId(s_RSMPass_Str);

(5)In the HDRenderPipeline.LightLoop.cs file, I bound the global texture ID

        static void BindGlobalRSMPassBuffers(in RSMGIPassData passBuffer, CommandBuffer cmd)
        {
            cmd.SetGlobalTexture(HDShaderIDs.RSMPassPositionVis, passBuffer.rsmGIPassData_rsmgiBuffers.rsmPositionBuffer);
            cmd.SetGlobalTexture(HDShaderIDs.RSMPassNormalVis, passBuffer.rsmGIPassData_rsmgiBuffers.rsmNormalBuffer);
            cmd.SetGlobalTexture(HDShaderIDs.RSMPassfluxVis, passBuffer.rsmGIPassData_rsmgiBuffers.rsmFluxBuffer);
        }

Also in the HDStringConstants.cs file,

        public static readonly int RSMPassPositionVis = Shader.PropertyToID("outPositionWS");
        public static readonly int RSMPassNormalVis = Shader.PropertyToID("outNormalWS");
        public static readonly int RSMPassfluxVis = Shader.PropertyToID("outColor");

This is a bit out of my knowledge range, but from what I see the texture and written to and read from in the rendergraph viewer. Maybe try to debug with RenderDoc to see if you find more informations there ?