Quick answer: Render Graph culls any pass whose outputs nothing else reads. If your pass writes to a private texture but no later pass samples it, the compiler removes it. Either write to the active camera color (so the final blit picks it up) or call builder.SetGlobalTextureAfterPass so other shaders can sample it.

Here is how to fix Unity URP 17+ Render Graph passes that compile clean, register fine, but never execute on screen. You migrated your custom ScriptableRenderPass from Execute to RecordRenderGraph, your code runs through AddRasterRenderPass, but the SetRenderFunc lambda never fires. Render Graph culls passes whose outputs are never consumed, and many migrations accidentally produce orphan passes.

The Symptom

Your custom Render Feature is in the URP Renderer Data list. RecordRenderGraph is being called (you can verify with a Debug.Log in the override). Inside, AddRasterRenderPass returns a builder, you wire it up — but the SetRenderFunc never fires at runtime. The Render Graph Viewer shows your pass in red, marked “Culled”.

What Causes This

No output is read downstream. Render Graph performs aggressive dead-pass elimination. If you write to a TextureHandle that no later pass declares via UseTexture(handle, AccessFlags.Read) or that does not feed the final present, your pass is removed.

Writing to active color but not declaring it. The active camera color must be declared with UseTextureFragment at AccessFlags.Write or AccessFlags.ReadWrite. Without that declaration, the graph thinks you wrote nothing.

Empty SetRenderFunc. If you allocate the pass but never call builder.SetRenderFunc, the compiler treats the pass as a no-op and culls it.

Conditional injection. If SetupRenderPasses only enqueues the pass under specific conditions (camera tag, debug flag), you may be looking at a frame where the condition was false.

The Fix

Step 1: Make the pass write to the camera color and declare it.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;

public class TintPass : ScriptableRenderPass
{
    private class PassData
    {
        public Material material;
        public TextureHandle source;
    }

    public Material tintMaterial;

    public override void RecordRenderGraph(RenderGraph rg, ContextContainer frameData)
    {
        var resources = frameData.Get<UniversalResourceData>();
        TextureHandle camColor = resources.activeColorTexture;

        using (var builder = rg.AddRasterRenderPass<PassData>("Tint", out var data))
        {
            data.material = tintMaterial;
            data.source = camColor;

            builder.UseTexture(camColor, AccessFlags.Read);
            builder.SetRenderAttachment(camColor, 0, AccessFlags.Write);
            builder.AllowPassCulling(false);

            builder.SetRenderFunc((PassData d, RasterGraphContext ctx) =>
            {
                Blitter.BlitTexture(ctx.cmd, d.source, new Vector4(1,1,0,0), d.material, 0);
            });
        }
    }
}

Step 2: Disable culling for debug. builder.AllowPassCulling(false) forces the pass to run regardless of output reads. Useful while diagnosing whether culling is the problem. Remove it after you have the right reads/writes declared.

Step 3: Inspect Render Graph Viewer. Open Window → Analysis → Render Graph Viewer. Pick a frame from the active camera. Find your pass — it should be green (executed). Red means culled. Hovering over a red pass shows the cull reason.

Step 4: Confirm the Render Feature enqueues the pass.

public class TintFeature : ScriptableRendererFeature
{
    [SerializeField] private Material material;
    private TintPass pass;

    public override void Create()
    {
        pass = new TintPass { tintMaterial = material, renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing };
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData data)
    {
        if (material == null) return;
        renderer.EnqueuePass(pass);
    }
}

If EnqueuePass is gated on conditions, log when those conditions are not met. Common gotcha: only enqueue for game cameras and skip the editor preview camera, which makes testing painful.

Step 5: For samplers in later passes, expose via SetGlobalTextureAfterPass.

builder.SetGlobalTextureAfterPass(myTexture, Shader.PropertyToID("_MyEffectTex"));

This makes the texture available to global shader sampling for the rest of the frame, which counts as a downstream read and prevents culling.

Migration Checklist

“Render Graph runs only what is reachable. Declare your reads, declare your writes, set the render func, or get culled.”

Related Issues

For URP render features in general, see URP Render Feature Not Executing. For shader-time issues, see Shader Graph Time Node Build Issues.

UseTexture for inputs. SetRenderAttachment for outputs. SetRenderFunc to do work. Disable culling while debugging.