Skip to content

Instantly share code, notes, and snippets.

@ScottJDaley
Created September 6, 2022 19:31
Show Gist options
  • Save ScottJDaley/db06257d35dce5de29f20dbe5b322be1 to your computer and use it in GitHub Desktop.
Save ScottJDaley/db06257d35dce5de29f20dbe5b322be1 to your computer and use it in GitHub Desktop.
Example of a URP Renderer Feature that can render objects (by layer mask) to a global texture
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class RenderObjectsToTextureFeature : ScriptableRendererFeature
{
public RenderObjectsToTexturePass.Settings Settings = new();
private RenderObjectsToTexturePass _renderPass;
public override void Create()
{
_renderPass = new RenderObjectsToTexturePass(name, Settings);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
_renderPass.Setup(renderingData.cameraData.cameraTargetDescriptor);
renderer.EnqueuePass(_renderPass);
}
}
public class RenderObjectsToTexturePass : ScriptableRenderPass
{
private const int DepthBufferBits = 32;
private readonly Settings _settings;
private RenderTextureDescriptor _descriptor;
private FilteringSettings _filteringSettings;
private RenderTargetHandle _tempTextureHandle;
private RenderTargetHandle _textureHandle;
[Serializable]
public class Settings
{
[Flags]
public enum LightModeTags
{
None = 0,
SRPDefaultUnlit = 1 << 0,
UniversalForward = 1 << 1,
UniversalForwardOnly = 1 << 2,
LightweightForward = 1 << 3,
DepthNormals = 1 << 4,
DepthOnly = 1 << 5,
Standard = SRPDefaultUnlit | UniversalForward | UniversalForwardOnly | LightweightForward,
}
public Material Material;
public int MaterialPassIndex = -1; // -1 means render all passes
public Material BlitMaterial;
public int BlitMaterialPassIndex = -1; // -1 means render all passes
public RenderPassEvent RenderPassEvent = RenderPassEvent.AfterRenderingOpaques;
public ScriptableRenderPassInput RenderPassInput = ScriptableRenderPassInput.None;
[Range(0, 5000)]
public int RenderQueueLowerBound;
[Range(0, 5000)]
public int RenderQueueUpperBound = 2499;
public RenderTextureFormat ColorFormat = RenderTextureFormat.ARGB32;
public SortingCriteria SortingCriteria = SortingCriteria.CommonOpaque;
public LayerMask LayerMask = -1;
public string TextureName = "_MyTexture";
public LightModeTags LightMode = LightModeTags.Standard;
public GlobalKeyword[] GlobalShaderKeywords;
[Serializable]
public struct GlobalKeyword
{
public enum Mode
{
None,
Enable,
Disable,
}
public string Name;
public bool Disabled;
public Mode BeforeRenderMode;
public Mode AfterRenderMode;
}
public RenderQueueRange RenderQueueRange => new(RenderQueueLowerBound, RenderQueueUpperBound);
public List<ShaderTagId> LightModeShaderTags
{
get
{
var tags = new List<ShaderTagId>();
if (LightMode.HasFlag(LightModeTags.SRPDefaultUnlit))
{
tags.Add(new ShaderTagId("SRPDefaultUnlit"));
}
if (LightMode.HasFlag(LightModeTags.UniversalForward))
{
tags.Add(new ShaderTagId("UniversalForward"));
}
if (LightMode.HasFlag(LightModeTags.UniversalForwardOnly))
{
tags.Add(new ShaderTagId("UniversalForwardOnly"));
}
if (LightMode.HasFlag(LightModeTags.LightweightForward))
{
tags.Add(new ShaderTagId("LightweightForward"));
}
if (LightMode.HasFlag(LightModeTags.DepthNormals))
{
tags.Add(new ShaderTagId("DepthNormals"));
}
if (LightMode.HasFlag(LightModeTags.DepthOnly))
{
tags.Add(new ShaderTagId("DepthOnly"));
}
return tags;
}
}
}
public RenderObjectsToTexturePass(string profilingName, Settings settings)
{
_settings = settings;
renderPassEvent = settings.RenderPassEvent;
profilingSampler = new ProfilingSampler(profilingName);
_filteringSettings = new FilteringSettings(settings.RenderQueueRange, settings.LayerMask.value);
_textureHandle.Init(settings.TextureName);
_tempTextureHandle.Init("_TempBlitMaterialTexture");
}
public void Setup(RenderTextureDescriptor baseDescriptor)
{
baseDescriptor.colorFormat = _settings.ColorFormat;
baseDescriptor.depthBufferBits = DepthBufferBits;
// Depth-Only pass don't use MSAA
baseDescriptor.msaaSamples = 1;
_descriptor = baseDescriptor;
ConfigureInput(_settings.RenderPassInput);
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
cmd.GetTemporaryRT(_textureHandle.id, _descriptor, FilterMode.Point);
ConfigureTarget(_textureHandle.Identifier());
ConfigureClear(ClearFlag.All, Color.clear);
cmd.GetTemporaryRT(_tempTextureHandle.id, _descriptor, FilterMode.Point);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
DrawingSettings drawingSettings = CreateDrawingSettings(
_settings.LightModeShaderTags,
ref renderingData,
_settings.SortingCriteria
);
drawingSettings.overrideMaterial = _settings.Material;
drawingSettings.overrideMaterialPassIndex = _settings.MaterialPassIndex;
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, profilingSampler))
{
UpdateKeywordsBeforeRender(cmd);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
if (_settings.BlitMaterial != null)
{
Blit(
cmd,
_textureHandle.Identifier(),
_tempTextureHandle.Identifier(),
_settings.BlitMaterial,
_settings.BlitMaterialPassIndex
);
Blit(cmd, _tempTextureHandle.Identifier(), _textureHandle.Identifier());
}
cmd.SetGlobalTexture(_settings.TextureName, _textureHandle.Identifier());
UpdateKeywordsAfterRender(cmd);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
if (cmd == null)
{
throw new ArgumentNullException("cmd");
}
cmd.ReleaseTemporaryRT(_tempTextureHandle.id);
}
private void UpdateKeywordsBeforeRender(CommandBuffer cmd)
{
if (_settings.GlobalShaderKeywords == null)
{
return;
}
foreach (Settings.GlobalKeyword keyword in _settings.GlobalShaderKeywords)
{
if (keyword.Disabled)
{
continue;
}
switch (keyword.BeforeRenderMode)
{
case Settings.GlobalKeyword.Mode.None:
break;
case Settings.GlobalKeyword.Mode.Enable:
cmd.EnableShaderKeyword(keyword.Name);
break;
case Settings.GlobalKeyword.Mode.Disable:
cmd.DisableShaderKeyword(keyword.Name);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private void UpdateKeywordsAfterRender(CommandBuffer cmd)
{
if (_settings.GlobalShaderKeywords == null)
{
return;
}
foreach (Settings.GlobalKeyword keyword in _settings.GlobalShaderKeywords)
{
if (keyword.Disabled)
{
continue;
}
switch (keyword.AfterRenderMode)
{
case Settings.GlobalKeyword.Mode.None:
break;
case Settings.GlobalKeyword.Mode.Enable:
cmd.EnableShaderKeyword(keyword.Name);
break;
case Settings.GlobalKeyword.Mode.Disable:
cmd.DisableShaderKeyword(keyword.Name);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
@vidocco
Copy link

vidocco commented Jun 5, 2023

Just wanted to leave a comment saying that this was RIDICULOUSLY helpful. Huge thanks for sharing 🙏

@ScottJDaley
Copy link
Author

Just wanted to leave a comment saying that this was RIDICULOUSLY helpful. Huge thanks for sharing 🙏

Glad you found it helpful!

@makinori
Copy link

I can't seem to figure out how to draw world space canvas'. DrawUIOverlay and DrawGizmos both work hmm

@anonymous2585
Copy link

anonymous2585 commented May 27, 2024

Hi,
In URP 16+, RenderTargetHandle and ScriptableRenderContext.DrawRenderers(...) are deprecated.

Here is the same script fixed for URP 16+

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class RenderObjectsToTextureFeature : ScriptableRendererFeature
{
    public RenderObjectsToTexturePass.Settings Settings = new();

    private RenderObjectsToTexturePass _renderPass;

    public override void Create()
    {
        _renderPass = new RenderObjectsToTexturePass(name, Settings);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(_renderPass);
    }

    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
        _renderPass.Setup(renderingData.cameraData.cameraTargetDescriptor);
    }

    protected override void Dispose(bool disposing)
    {
        _renderPass?.Dispose();
    }
}

public class RenderObjectsToTexturePass : ScriptableRenderPass
{
    private const int DepthBufferBits = 32;

    private readonly Settings _settings;
    private RenderTextureDescriptor _descriptor;
    private FilteringSettings _filteringSettings;
    private RTHandle _tempTextureHandle;
    private RTHandle _textureHandle;
    private RTHandle _textureHandleDepth;


    [Serializable]
    public class Settings
    {
        [Flags]
        public enum LightModeTags
        {
            None = 0,
            SRPDefaultUnlit = 1 << 0,
            UniversalForward = 1 << 1,
            UniversalForwardOnly = 1 << 2,
            LightweightForward = 1 << 3,
            DepthNormals = 1 << 4,
            DepthOnly = 1 << 5,
            Standard = SRPDefaultUnlit | UniversalForward | UniversalForwardOnly | LightweightForward,
        }

        public Material Material;
        public int MaterialPassIndex = -1; // -1 means render all passes
        public Material BlitMaterial;
        public int BlitMaterialPassIndex = -1; // -1 means render all passes
        public RenderPassEvent RenderPassEvent = RenderPassEvent.AfterRenderingOpaques;
        public ScriptableRenderPassInput RenderPassInput = ScriptableRenderPassInput.None;

        [Range(0, 5000)]
        public int RenderQueueLowerBound;

        [Range(0, 5000)]
        public int RenderQueueUpperBound = 2499;

        public RenderTextureFormat ColorFormat = RenderTextureFormat.ARGB32;
        public SortingCriteria SortingCriteria = SortingCriteria.CommonOpaque;
        public LayerMask LayerMask = -1;
        public string TextureName = "_MyTexture";

        public LightModeTags LightMode = LightModeTags.Standard;

        public GlobalKeyword[] GlobalShaderKeywords;

        [Serializable]
        public struct GlobalKeyword
        {
            public enum Mode
            {
                None,
                Enable,
                Disable,
            }

            public string Name;
            public bool Disabled;

            public Mode BeforeRenderMode;
            public Mode AfterRenderMode;
        }

        public RenderQueueRange RenderQueueRange => new(RenderQueueLowerBound, RenderQueueUpperBound);

        public List<ShaderTagId> LightModeShaderTags
        {
            get
            {
                var tags = new List<ShaderTagId>();
                if (LightMode.HasFlag(LightModeTags.SRPDefaultUnlit))
                {
                    tags.Add(new ShaderTagId("SRPDefaultUnlit"));
                }
                if (LightMode.HasFlag(LightModeTags.UniversalForward))
                {
                    tags.Add(new ShaderTagId("UniversalForward"));
                }
                if (LightMode.HasFlag(LightModeTags.UniversalForwardOnly))
                {
                    tags.Add(new ShaderTagId("UniversalForwardOnly"));
                }
                if (LightMode.HasFlag(LightModeTags.LightweightForward))
                {
                    tags.Add(new ShaderTagId("LightweightForward"));
                }
                if (LightMode.HasFlag(LightModeTags.DepthNormals))
                {
                    tags.Add(new ShaderTagId("DepthNormals"));
                }
                if (LightMode.HasFlag(LightModeTags.DepthOnly))
                {
                    tags.Add(new ShaderTagId("DepthOnly"));
                }
                return tags;
            }
        }
    }

    public RenderObjectsToTexturePass(string profilingName, Settings settings)
    {
        _settings = settings;
        renderPassEvent = settings.RenderPassEvent;
        profilingSampler = new ProfilingSampler(profilingName);
        _filteringSettings = new FilteringSettings(settings.RenderQueueRange, settings.LayerMask.value);
    }

    public void Setup(RenderTextureDescriptor baseDescriptor)
    {
        baseDescriptor.colorFormat = _settings.ColorFormat;
        baseDescriptor.depthBufferBits = DepthBufferBits;

        // Depth-Only pass don't use MSAA
        baseDescriptor.msaaSamples = 1;
        _descriptor = baseDescriptor;

        ConfigureInput(_settings.RenderPassInput);
    }


    public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    {
        RenderingUtils.ReAllocateIfNeeded(ref _textureHandleDepth, _descriptor, name: _settings.TextureName + "Depth");
        RenderTextureDescriptor descColor = _descriptor;
        descColor.depthBufferBits = 0; // No depth for the color texture
        RenderingUtils.ReAllocateIfNeeded(ref _textureHandle, descColor, name: _settings.TextureName);

        ConfigureTarget(_textureHandle, _textureHandleDepth);
        ConfigureClear(ClearFlag.All, Color.clear);
        RenderingUtils.ReAllocateIfNeeded(ref _tempTextureHandle, descColor, name: "_TempBlitMaterialTexture");
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        DrawingSettings drawingSettings = CreateDrawingSettings(
            _settings.LightModeShaderTags,
            ref renderingData,
            _settings.SortingCriteria
        );

        drawingSettings.overrideMaterial = _settings.Material;
        drawingSettings.overrideMaterialPassIndex = _settings.MaterialPassIndex;

        CommandBuffer cmd = CommandBufferPool.Get();
        using (new ProfilingScope(cmd, profilingSampler))
        {
            UpdateKeywordsBeforeRender(cmd);

            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();

            RendererListParams rendererListParams = new RendererListParams(renderingData.cullResults, drawingSettings, _filteringSettings);
            RendererList list = context.CreateRendererList(ref rendererListParams);
            cmd.DrawRendererList(list);

            if (_settings.BlitMaterial != null)
            {
                Blit(
                    cmd,
                    _textureHandle,
                    _tempTextureHandle,
                    _settings.BlitMaterial,
                    _settings.BlitMaterialPassIndex
                );
                Blit(cmd, _tempTextureHandle, _textureHandle);
            }

            cmd.SetGlobalTexture(_settings.TextureName, _textureHandle);

            UpdateKeywordsAfterRender(cmd);
        }

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

    public override void OnCameraCleanup(CommandBuffer cmd)
    {
        if (cmd == null)
        {
            throw new ArgumentNullException("cmd");
        }
    }

    public void Dispose()
    {
        _tempTextureHandle?.Release();
        _textureHandleDepth?.Release();
        _textureHandle?.Release();
    }

    private void UpdateKeywordsBeforeRender(CommandBuffer cmd)
    {
        if (_settings.GlobalShaderKeywords == null)
        {
            return;
        }
        foreach (Settings.GlobalKeyword keyword in _settings.GlobalShaderKeywords)
        {
            if (keyword.Disabled)
            {
                continue;
            }
            switch (keyword.BeforeRenderMode)
            {
                case Settings.GlobalKeyword.Mode.None:
                    break;
                case Settings.GlobalKeyword.Mode.Enable:
                    cmd.EnableShaderKeyword(keyword.Name);
                    break;
                case Settings.GlobalKeyword.Mode.Disable:
                    cmd.DisableShaderKeyword(keyword.Name);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    private void UpdateKeywordsAfterRender(CommandBuffer cmd)
    {
        if (_settings.GlobalShaderKeywords == null)
        {
            return;
        }
        foreach (Settings.GlobalKeyword keyword in _settings.GlobalShaderKeywords)
        {
            if (keyword.Disabled)
            {
                continue;
            }
            switch (keyword.AfterRenderMode)
            {
                case Settings.GlobalKeyword.Mode.None:
                    break;
                case Settings.GlobalKeyword.Mode.Enable:
                    cmd.EnableShaderKeyword(keyword.Name);
                    break;
                case Settings.GlobalKeyword.Mode.Disable:
                    cmd.DisableShaderKeyword(keyword.Name);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}

@Ewoud3D
Copy link

Ewoud3D commented Dec 13, 2024

This is a huge lifesaver!
Has there been an updated script, for Unity 6 Render graph?

@ScottJDaley
Copy link
Author

I haven't tested this with the latest versions of unity, but I did update it to use render graph at some point for a project of mine. Here is the code for the render pass:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;


public class RenderTexturePass : ScriptableRenderPass
{
    private Settings _settings;
    private RenderTextureDescriptor _descriptor;

    private RenderStateBlock _renderStateBlock;

    [Serializable]
    public class Settings
    {
        [Flags]
        public enum LightModeTags
        {
            None = 0,

            // ReSharper disable once InconsistentNaming
            SRPDefaultUnlit = 1 << 0,
            UniversalForward = 1 << 1,
            UniversalForwardOnly = 1 << 2,
            LightweightForward = 1 << 3,
            DepthNormals = 1 << 4,
            DepthOnly = 1 << 5,
            DepthNormalsOnly = 1 << 6,
            Standard = SRPDefaultUnlit | UniversalForward | UniversalForwardOnly | LightweightForward,
        }

        public Material Material;
        public int MaterialPassIndex = -1; // -1 means render all passes

        // TODO: Add support for doing a blit after rendering the objects to a texture
        // public Material BlitMaterial;
        // public int BlitMaterialPassIndex = -1; // -1 means render all passes

        public RenderPassEvent RenderPassEvent = RenderPassEvent.AfterRenderingOpaques;
        public ScriptableRenderPassInput RenderPassInput = ScriptableRenderPassInput.None;

        [Range(0, 5000)]
        public int RenderQueueLowerBound;

        [Range(0, 5000)]
        public int RenderQueueUpperBound = 2499;

        public RenderTextureFormat ColorFormat = RenderTextureFormat.ARGB32;
        public SortingCriteria SortingCriteria = SortingCriteria.CommonOpaque;
        public LayerMask LayerMask = ~0;
        public LightLayerEnum RenderLayerMask = LightLayerEnum.Everything;
        public string TextureName = "_MyTexture";

        public LightModeTags LightMode = LightModeTags.Standard;

        public GlobalKeyword[] GlobalShaderKeywords;
        public List<string> ShaderTags;

        public bool Depth;
        public bool WriteDepth;
        public CompareFunction DepthCompare = CompareFunction.LessEqual;

        [Serializable]
        public struct GlobalKeyword
        {
            public enum Mode
            {
                None,
                Enable,
                Disable,
            }

            public string Name;
            public bool Disabled;

            public Mode BeforeRenderMode;
            public Mode AfterRenderMode;
        }

        public RenderQueueRange RenderQueueRange => new(RenderQueueLowerBound, RenderQueueUpperBound);

        public List<ShaderTagId> LightModeShaderTags
        {
            get
            {
                var tags = new List<ShaderTagId>();
                if (LightMode.HasFlag(LightModeTags.SRPDefaultUnlit))
                {
                    tags.Add(new ShaderTagId("SRPDefaultUnlit"));
                }
                if (LightMode.HasFlag(LightModeTags.UniversalForward))
                {
                    tags.Add(new ShaderTagId("UniversalForward"));
                }
                if (LightMode.HasFlag(LightModeTags.UniversalForwardOnly))
                {
                    tags.Add(new ShaderTagId("UniversalForwardOnly"));
                }
                if (LightMode.HasFlag(LightModeTags.LightweightForward))
                {
                    tags.Add(new ShaderTagId("LightweightForward"));
                }
                if (LightMode.HasFlag(LightModeTags.DepthNormals))
                {
                    tags.Add(new ShaderTagId("DepthNormals"));
                }
                if (LightMode.HasFlag(LightModeTags.DepthNormalsOnly))
                {
                    tags.Add(new ShaderTagId("DepthNormalsOnly"));
                }
                if (LightMode.HasFlag(LightModeTags.DepthOnly))
                {
                    tags.Add(new ShaderTagId("DepthOnly"));
                }
                if (ShaderTags != null)
                {
                    foreach (string tag in ShaderTags)
                    {
                        tags.Add(new ShaderTagId(tag));
                    }
                }
                return tags;
            }
        }
    }

    private class PassData
    {
        public RendererListHandle RendererListHandle;
        public Settings.GlobalKeyword[] GlobalKeywords;
    }

    public void Setup(string profilingName, Settings settings)
    {
        _settings = settings;
        renderPassEvent = _settings.RenderPassEvent;
        profilingSampler = new ProfilingSampler(profilingName);
        _renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
        // Debug.Log($"RenderTexturePass: depth? {_settings.Depth}");
        if (_settings.Depth)
        {
            _renderStateBlock.mask |= RenderStateMask.Depth;
            bool writeEnabled = _settings.WriteDepth;
            CompareFunction function = _settings.DepthCompare;
            _renderStateBlock.depthState = new DepthState(writeEnabled, function);
        }
        ConfigureInput(settings.RenderPassInput);
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        using IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(
            _settings.TextureName,
            out PassData passData,
            profilingSampler
        );

        // Initialize the pass data
        InitPassData(renderGraph, frameData, ref passData);

        // Create the destination texture
        TextureHandle destination = CreateDestinationTexture(renderGraph, frameData);

        // Make sure the renderer list is valid
        if (!passData.RendererListHandle.IsValid())
        {
            return;
        }

        // We declare the RendererList we just created as an input dependency to this pass, via UseRendererList()
        builder.UseRendererList(passData.RendererListHandle);

        // Setup as a render target via UseTextureFragment, which is the equivalent of using the old cmd.SetRenderTarget
        builder.SetRenderAttachment(destination, 0);
        // builder.SetRenderAttachmentDepth(
        //     resourceData.activeDepthTexture,
        //     _settings.WriteDepth ? AccessFlags.ReadWrite : AccessFlags.Read
        // );
        builder.SetGlobalTextureAfterPass(destination, Shader.PropertyToID(_settings.TextureName));

        // Shader keyword changes are considered as global state modifications
        builder.AllowGlobalStateModification(true);
        builder.AllowPassCulling(false);

        // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
        builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));
    }

    // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    private static void ExecutePass(PassData data, RasterGraphContext context)
    {
        UpdateKeywordsBeforeRender(data, context.cmd);

        context.cmd.ClearRenderTarget(RTClearFlags.ColorDepth, Color.black, 0, 0);

        context.cmd.DrawRendererList(data.RendererListHandle);

        UpdateKeywordsAfterRender(data, context.cmd);
    }

    private static void UpdateKeywordsBeforeRender(PassData data, RasterCommandBuffer cmd)
    {
        if (data.GlobalKeywords == null)
        {
            return;
        }
        foreach (Settings.GlobalKeyword keyword in data.GlobalKeywords)
        {
            if (keyword.Disabled)
            {
                continue;
            }
            switch (keyword.BeforeRenderMode)
            {
                case Settings.GlobalKeyword.Mode.None:
                    break;
                case Settings.GlobalKeyword.Mode.Enable:
                    cmd.EnableShaderKeyword(keyword.Name);
                    break;
                case Settings.GlobalKeyword.Mode.Disable:
                    cmd.DisableShaderKeyword(keyword.Name);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    private static void UpdateKeywordsAfterRender(PassData data, RasterCommandBuffer cmd)
    {
        if (data.GlobalKeywords == null)
        {
            return;
        }
        foreach (Settings.GlobalKeyword keyword in data.GlobalKeywords)
        {
            if (keyword.Disabled)
            {
                continue;
            }
            switch (keyword.AfterRenderMode)
            {
                case Settings.GlobalKeyword.Mode.None:
                    break;
                case Settings.GlobalKeyword.Mode.Enable:
                    cmd.EnableShaderKeyword(keyword.Name);
                    break;
                case Settings.GlobalKeyword.Mode.Disable:
                    cmd.DisableShaderKeyword(keyword.Name);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    private void InitPassData(RenderGraph renderGraph, ContextContainer frameData, ref PassData passData)
    {
        // Fill up the passData with the data needed by the passes

        // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
        // The active color and depth textures are the main color and depth buffers that the camera renders into
        var universalRenderingData = frameData.Get<UniversalRenderingData>();

        // The destination texture is created here, 
        // the texture is created with the same dimensions as the active color texture, but with no depth buffer, being a copy of the color texture
        // we also disable MSAA as we don't need multisampled textures for this sample

        var cameraData = frameData.Get<UniversalCameraData>();
        var lightData = frameData.Get<UniversalLightData>();

        DrawingSettings drawingSettings = RenderingUtils.CreateDrawingSettings(
            _settings.LightModeShaderTags,
            universalRenderingData,
            cameraData,
            lightData,
            _settings.SortingCriteria
        );
        drawingSettings.overrideMaterial = _settings.Material;
        drawingSettings.overrideMaterialPassIndex = _settings.MaterialPassIndex;

        var filteringSettings = new FilteringSettings(
            _settings.RenderQueueRange,
            _settings.LayerMask,
            (uint)_settings.RenderLayerMask
        );

        RendererListHandle renderListHandle = RenderingHelpers.CreateRendererListWithRenderStateBlock(
            renderGraph,
            ref universalRenderingData.cullResults,
            drawingSettings,
            filteringSettings,
            _renderStateBlock
        );

        passData.RendererListHandle = renderListHandle;
        passData.GlobalKeywords = _settings.GlobalShaderKeywords;
    }

    private TextureHandle CreateDestinationTexture(RenderGraph renderGraph, ContextContainer frameData)
    {
        var cameraData = frameData.Get<UniversalCameraData>();
        RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
        desc.colorFormat = _settings.ColorFormat;
        desc.depthBufferBits = 0;
        desc.msaaSamples = 1;
        TextureHandle destination = UniversalRenderer.CreateRenderGraphTexture(
            renderGraph,
            desc,
            _settings.TextureName,
            false
        );

        return destination;
    }
}

Note: you'll need to use this render pass in a RendererFeature like in the original code though. I can try to share a more complete script at some point when I have time.

@Ewoud3D
Copy link

Ewoud3D commented Dec 14, 2024

Thank you! 🙌

@Shaun-Fong
Copy link

Looks like RenderTexturePass missing RenderingHelpers.

@ScottJDaley
Copy link
Author

Looks like RenderTexturePass missing RenderingHelpers.

Oops, I guess that's what happens when you try copy and paste a bunch of code on your phone. Here is the RenderingHelpers class. It is worth noting that this is just a copy of an internal unity function inside of RenderingUtils.

using Unity.Collections;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;

public static class RenderingHelpers
{
    private static readonly ShaderTagId[] _shaderTagValues = new ShaderTagId[1];
    private static readonly RenderStateBlock[] _renderStateBlocks = new RenderStateBlock[1];

    // --- Copied from internal method RenderingUtils.CreateRendererListWithRenderStateBlock() ---
    // Create a RendererList using a RenderStateBlock override is quite common so we have this optimized utility function for it
    public static RendererListHandle CreateRendererListWithRenderStateBlock(
        RenderGraph renderGraph,
        ref CullingResults cullResults,
        DrawingSettings drawingSettings,
        FilteringSettings filteringSettings,
        RenderStateBlock renderStateBlock)
    {
        _shaderTagValues[0] = ShaderTagId.none;
        _renderStateBlocks[0] = renderStateBlock;
        var tagValues = new NativeArray<ShaderTagId>(_shaderTagValues, Allocator.Temp);
        var stateBlocks = new NativeArray<RenderStateBlock>(_renderStateBlocks, Allocator.Temp);
        var param = new RendererListParams(cullResults, drawingSettings, filteringSettings)
        {
            tagValues = tagValues, stateBlocks = stateBlocks, isPassTagName = false,
        };
        return renderGraph.CreateRendererList(param);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment