Hi, I am trying to get a simple outline effect in Unity based on this tutorial Edge Detection Outlines
I am in Unity 2022.3.5 and URP 14.0.8 so I am also attempting to update the tutorial to the newer URP specs.When I do this, I get a black screen and multiple warnings and error logs.
NullReferenceException: Object reference not set to an instance of an object
UnityEngine.Rendering.Universal.ScriptableRenderPass.Blit
Render Pipeline error : the XR layout still contains active passes. Executing XRSystem.EndLayout() right now.
Internal: JobTempAlloc has allocations that are more than the maximum lifespan of 4 frames old - this is not allowed and likely a leak
To Debug, run app with -diag-job-temp-memory-leak-validation cmd line argument. This will output the callstacks of the leaked allocations.
The RenderFeature Scripts are attached. One is to combine depth and normals, in the frame debugger it looks like this is working fine, the other is the outline and is the source of the problem.
Any help is appreciated, thank you!
Also if there is a source for beginner friendly documentation on this stuff please let me know, a lot of it has been very opaque unless you already know how it works.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class OutlineFeature : ScriptableRendererFeature
{
class OutlinePass : ScriptableRenderPass
{
private RTHandle source { get; set; }
private RTHandle destination { get; set; }
public Material outlineMaterial = null;
RTHandle temporaryColorTexture;
public void Setup(RTHandle source, RTHandle destination)
{
this.source = source;
this.destination = destination;
}
public OutlinePass(Material outlineMaterial)
{
this.outlineMaterial = outlineMaterial;
}
// This method is called before executing the render pass.
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
// When empty this render pass will render to the active camera render target.
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
// The render pipeline will ensure target setup and clearing happens in an performance manner.
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
temporaryColorTexture = RTHandles.Alloc("_TemporaryColor", name: "_TemporaryColor");
}
// 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("_OutlinePass");
RenderTextureDescriptor opaqueDescriptor = renderingData.cameraData.cameraTargetDescriptor;
opaqueDescriptor.depthBufferBits = 0;
// Debug.Log("Executing outline renderer");
if (renderingData.cameraData.cameraType == CameraType.Game)
{
cmd.GetTemporaryRT(Shader.PropertyToID(temporaryColorTexture.name), opaqueDescriptor, FilterMode.Point);
Blit(cmd, source, temporaryColorTexture, outlineMaterial, 0);
Blit(cmd, temporaryColorTexture, source);
// Blit(cmd, source, source);
}
else
{
Blit(cmd, source, destination, outlineMaterial, 0);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
/// Cleanup any allocated resources that were created during the execution of this render pass.
public override void FrameCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(Shader.PropertyToID(temporaryColorTexture.name));
RTHandles.Release(temporaryColorTexture);
}
}
[System.Serializable]
public class OutlineSettings
{
public Material outlineMaterial = null;
}
public OutlineSettings settings = new OutlineSettings();
OutlinePass outlinePass;
RTHandle outlineTexture;
public override void Create()
{
outlinePass = new OutlinePass(settings.outlineMaterial);
outlinePass.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
outlineTexture = RTHandles.Alloc("_OutlineTexture", name: "_OutlineTexture");
// outlineTexture.Init("_OutlineTexture");
}
// 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)
{
if (settings.outlineMaterial == null)
{
Debug.LogWarningFormat("Missing Outline Material");
return;
}
renderer.EnqueuePass(outlinePass);
}
public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
{
outlinePass.Setup(renderer.cameraColorTargetHandle, renderer.cameraColorTargetHandle);
}
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class DepthNormalsFeature : ScriptableRendererFeature
{
class DepthNormalsPass : ScriptableRenderPass
{
int kDepthBufferBits = 32;
private RTHandle internalDepthAttachmentHandle { get; set; }
internal RenderTextureDescriptor descriptor { get; private set; }
private Material depthNormalsMaterial = null;
private FilteringSettings m_FilteringSettings;
string m_ProfilerTag = "DepthNormals Prepass";
ShaderTagId m_ShaderTagId = new ShaderTagId("DepthOnly");
public DepthNormalsPass(RenderQueueRange renderQueueRange, LayerMask layerMask, Material material)
{
m_FilteringSettings = new FilteringSettings(renderQueueRange, layerMask);
depthNormalsMaterial = material;
}
public void Setup(RenderTextureDescriptor baseDescriptor, RTHandle depthAttachmentHandle)
{
this.internalDepthAttachmentHandle = depthAttachmentHandle;
baseDescriptor.colorFormat = RenderTextureFormat.ARGB32;
baseDescriptor.depthBufferBits = kDepthBufferBits;
descriptor = baseDescriptor;
}
// This method is called before executing the render pass.
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
// When empty this render pass will render to the active camera render target.
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
// The render pipeline will ensure target setup and clearing happens in an performance manner.
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
cmd.GetTemporaryRT(Shader.PropertyToID(internalDepthAttachmentHandle.name), descriptor, FilterMode.Point);
ConfigureTarget(internalDepthAttachmentHandle);
ConfigureClear(ClearFlag.All, Color.black);
}
// 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(m_ProfilerTag);
using (new ProfilingScope(cmd, new ProfilingSampler(m_ProfilerTag)))
{
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
drawSettings.perObjectData = PerObjectData.None;
ref CameraData cameraData = ref renderingData.cameraData;
Camera camera = cameraData.camera;
if (cameraData.xrRendering)
context.StartMultiEye(camera);
drawSettings.overrideMaterial = depthNormalsMaterial;
context.DrawRenderers(renderingData.cullResults, ref drawSettings,
ref m_FilteringSettings);
cmd.SetGlobalTexture("_CameraDepthNormalsTexture", Shader.PropertyToID(internalDepthAttachmentHandle.name));
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
/// Cleanup any allocated resources that were created during the execution of this render pass.
public override void FrameCleanup(CommandBuffer cmd)
{
if (internalDepthAttachmentHandle != depthAttachmentHandle)
{
cmd.ReleaseTemporaryRT(Shader.PropertyToID(internalDepthAttachmentHandle.name));
internalDepthAttachmentHandle = depthAttachmentHandle;
}
}
}
DepthNormalsPass depthNormalsPass;
RTHandle depthNormalsTexture;
Material depthNormalsMaterial;
public override void Create()
{
depthNormalsMaterial = CoreUtils.CreateEngineMaterial("Hidden/Internal-DepthNormalsTexture");
depthNormalsPass = new DepthNormalsPass(RenderQueueRange.opaque, -1, depthNormalsMaterial);
depthNormalsPass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses;
// depthNormalsTexture.Init("_CameraDepthNormalsTexture");
depthNormalsTexture = RTHandles.Alloc("_CameraDepthNormalsTexture", name:"_CameraDepthNormalsTexture");
}
// 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)
{
depthNormalsPass.Setup(renderingData.cameraData.cameraTargetDescriptor, depthNormalsTexture);
renderer.EnqueuePass(depthNormalsPass);
}
}