Custom Render Pass Feature causing a black screen in build...

So I followed this:

tutorial on adding a custom post-processing effect which was made using a custom render pass feature that is applied to a forward renderer and applies a material to the whole screen. In my case, I created a simple shader in the URP shader graph which basically just changes the colour of each pixel based on its value.

That might not’ve made much sense but it works fine in the editor, visually its doing exactly what I hoped for but when I go to build it the game is just a black screen. I read a couple of other posts on custom render passes but they didn’t help. Ive also checked the player log and there’s no issue.

This is my first time doing anything like this and I’m really not sure whats wrong.
That was the code for my custom render pass feature…

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

public class CustomRenderPassFeature : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        public RenderTargetIdentifier source;
        Material mat;
        RenderTargetHandle tempRenderTarget;

        public CustomRenderPass(Material material)
        {
            mat = material;
            tempRenderTarget.Init("_temp");
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer commandBuffer = CommandBufferPool.Get();
            commandBuffer.GetTemporaryRT(tempRenderTarget.id, renderingData.cameraData.cameraTargetDescriptor);
            Blit(commandBuffer, source, tempRenderTarget.Identifier(), mat);
            Blit(commandBuffer, tempRenderTarget.Identifier(), source);

            context.ExecuteCommandBuffer(commandBuffer);
            CommandBufferPool.Release(commandBuffer);
        }

        public override void OnCameraCleanup(CommandBuffer cmd)
        {
        }
    }

    [System.Serializable]
    public class Settings
    {
        public Material material = null;
    }

    public Settings settings = new Settings();

    CustomRenderPass m_ScriptablePass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_ScriptablePass = new CustomRenderPass(settings.material)
        {
            renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing
        };
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        m_ScriptablePass.source = renderer.cameraColorTarget;
        renderer.EnqueuePass(m_ScriptablePass);
    }
}

It might also be worth noting that it works completely fine when the render feature is removed.

UI Also works fine in the build - although it is unaffected by the render feature.

I see a few potential issues that could be going on here.

1.You are initializing your scriptable pass with the material instead of applying it consistently, there is a chance that the custom render pass is created before your renderer feature has been unserialized in build potentially leading to a null material being assigned.

2.You are assigning the source target from the renderer not renderingData and before the pass is enqueued. When a pass is enqueued all the ScriptableRenderer passes run in a specific order, some calls are consecutive with each pass and some calls are not. You can’t ( I can’t at least) guarantee that the source target you set before the passes are executed exist or will be the same target by the time your execute is called. You should assign the target every time in Configure or OnCameraSetup.

3.You are retrieving temporary render textures but not releasing them.

here is my modified version of your script, check it over, I added a few things, it should work for you though I have not tried it with a build. If it does not work in build then its more than likely a Unity bug.

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

// Make sure script name matches the name of the class,
// I am using "MyCustomRenderPassFeature" instead of "CustomRenderPassFeature" here
public class MyCustomRenderPassFeature : ScriptableRendererFeature
{
  [System.Serializable]
  public class Settings
  {
    public Material material = null;
  }
  public Settings settings = new Settings();

  MyCustomRenderPass m_ScriptablePass;

  public override void Create()
  {
    m_ScriptablePass = new MyCustomRenderPass(settings)
    {
      renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing
    };
  }

  public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
  {
    // we dont wan't to run the pass if there is no material
    if (settings.material == null)
    {
      return;
    }
    // this will keep the preview cameras (Materials, Prefabs, ect) box in inspectors from being affected
    if (renderingData.cameraData.isPreviewCamera)
    {
      return;
    }

    renderer.EnqueuePass(m_ScriptablePass);
  }

  class MyCustomRenderPass : ScriptableRenderPass
  {
    // name this what you want, it will be used to name the profile in frame debugger
    const string profilingName = "My Custom Renderer Pass";

    // name this whatever you want, it will just be used to make your temp id
    const string destinationName = "_MyCustomTemp";

    // store settings instead of material itself
    Settings settings;

    // use int as id instead of RenderTargetHandle
    int destinationID;

    public RenderTargetIdentifier source;

    public MyCustomRenderPass(Settings settings)
    {
      // storing the settings allows you to add more features faster without having to boiler plate code,
      // also ensures that any changes made in the render feature reflect in the pass
      this.settings = settings;

      // well get a shader id instead of creating target handle
      this.destinationID = Shader.PropertyToID(destinationName);

      // create a new profiling sampler with are chosen name,
      // else you get just a generic "ScriptableRendererPass" name
      this.profilingSampler = new ProfilingSampler(profilingName);
    }

    public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    {
      // retrieve a temporary RT right before Execute using the destinationID
      cmd.GetTemporaryRT(destinationID, cameraTextureDescriptor);
    }

    public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    {
      // get the source target from rendering data every frame
      source = renderingData.cameraData.renderer.cameraColorTarget;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
      CommandBuffer cmd = CommandBufferPool.Get();
   
      // create new profiling scope
      // not needed but makes things nice in frame debugger
      using( var profilingScope= new ProfilingScope(cmd, profilingSampler))
      {
        // uncomment the set target portions if you still have issues 
        Blit(cmd, source, destinationID, settings.material);
        Blit(cmd, destinationID, source);

        // if you still have issues possibly try the code below instead
        // of the blit calls above
        /*
        cmd.SetRenderTarget(destinationID);
        cmd.Blit(source, destinationID, settings.material);
        cmd.SetRenderTarget(source);
        cmd.Blit(destinationID, source);
        */
      }
      // execute CommandBuffer then release it
      context.ExecuteCommandBuffer(cmd);
      CommandBufferPool.Release(cmd);
    }

    public override void OnCameraCleanup(CommandBuffer cmd)
    {
      // very important to release temporary RT's after use, all sorts of things can go wrong if you don't
      cmd.ReleaseTemporaryRT(destinationID);
    }
  }
}

I modified the code. Ok late reply but heres what u do … when u call CommandBufferPool.Get(“whateverpass”); pass in a string name as a parameter.

hey i got the same case, i picked it from here
MirzaBeig/Anime-Speed-Lines: Post-processing effect to procedurally generate a anime/manga-style vignette of lines typically used to portray speed or surprise. (github.com)

i modify it according to your guide, but still got a black screen. any idea?
but it’s not happening on all device.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Serialization;

public class SpeedLineRenderPassFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class CustomRenderPassSettings
    {
        public Material material;
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
    }

    SpeedLineRenderPass m_ScriptablePass;
  
    public CustomRenderPassSettings settings = new CustomRenderPassSettings();

    /// <inheritdoc/>
    ///
    public override void Create()
    {
        m_ScriptablePass = new SpeedLineRenderPass(settings);
    }

    // Here you can inject one or multiple render passes in the renderer.
    // This method is called when setting up the renderer once per-camera.

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        // we dont wan't to run the pass if there is no material
        if (settings.material == null)
        {
            return;
        }
        //RenderTargetIdentifier source = renderer.cameraColorTarget;
        //m_ScriptablePass.Setup(source);
        renderer.EnqueuePass(m_ScriptablePass);
    }


    class SpeedLineRenderPass : ScriptableRenderPass
    {
        RenderTargetIdentifier source;

        // store settings instead of material itself
        SpeedLineRenderPassFeature.CustomRenderPassSettings settings;

        // name this what you want, it will be used to name the profile in frame debugger
        const string profilingName = "SpeedLineRender Pass";

        // name this whatever you want, it will just be used to make your temp id
        const string destinationName = "_SpeedlineRenderDest";

        // use int as id instead of RenderTargetHandle
        int destinationID;


        public SpeedLineRenderPass(SpeedLineRenderPassFeature.CustomRenderPassSettings settings)
        {
            // storing the settings allows you to add more features faster without having to boiler plate code,
            // also ensures that any changes made in the render feature reflect in the pass
            this.settings = settings;
            renderPassEvent = settings.renderPassEvent;

            // well get a shader id instead of creating target handle
            this.destinationID = Shader.PropertyToID(destinationName);

            // create a new profiling sampler with are chosen name,
            // else you get just a generic "ScriptableRendererPass" name
            this.profilingSampler = new ProfilingSampler(profilingName);
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            // retrieve a temporary RT right before Execute using the destinationID
            cmd.GetTemporaryRT(destinationID, cameraTextureDescriptor);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // get the source target from rendering data every frame
            source = renderingData.cameraData.renderer.cameraColorTargetHandle;
        }

        // Here you can implement the rendering logic.
        // Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers
        // https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
        // You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(nameof(SpeedLineRenderPass));

            // create new profiling scope
            // not needed but makes things nice in frame debugger
            using (var profilingScope = new ProfilingScope(cmd, profilingSampler))
            {
                cmd.SetRenderTarget(destinationID);
                cmd.Blit(source, destinationID, settings.material);
                cmd.SetRenderTarget(source);
                cmd.Blit(destinationID, source);
            }

            // execute CommandBuffer then release it
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        // Cleanup any allocated resources that were created during the execution of this render pass.

        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(destinationID);
        }
    }
}

Perform a full screen blit in URP | Universal RP | 14.0.9 (unity3d.com)
Unity - Manual: ShaderLab: adding shader programs (unity3d.com)

Solved.
i put my solution on this Issue, for somone who’s looking at it
Not Working on Latest URP 14+, black screen on build · Issue #1 · MirzaBeig/Anime-Speed-Lines (github.com)