Skip to content

Instantly share code, notes, and snippets.

Last active September 26, 2023 11:36
Show Gist options
  • Save Cyanilux/3e723b923c4f43d315ad5a55685b5cb9 to your computer and use it in GitHub Desktop.
Save Cyanilux/3e723b923c4f43d315ad5a55685b5cb9 to your computer and use it in GitHub Desktop.
Custom ScriptableRendererFeature/ScriptableRenderPass example for URP 2022+. From
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class CustomRendererFeature : ScriptableRendererFeature {
public class CustomRenderPass : ScriptableRenderPass {
private Settings settings;
private FilteringSettings filteringSettings;
private ProfilingSampler _profilingSampler;
private List<ShaderTagId> shaderTagsList = new List<ShaderTagId>();
private RTHandle rtCustomColor, rtTempColor;
public CustomRenderPass(Settings settings, string name) {
this.settings = settings;
filteringSettings = new FilteringSettings(RenderQueueRange.opaque, settings.layerMask);
// Use default tags
shaderTagsList.Add(new ShaderTagId("SRPDefaultUnlit"));
shaderTagsList.Add(new ShaderTagId("UniversalForward"));
shaderTagsList.Add(new ShaderTagId("UniversalForwardOnly"));
_profilingSampler = new ProfilingSampler(name);
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {
var colorDesc = renderingData.cameraData.cameraTargetDescriptor;
colorDesc.depthBufferBits = 0;
// Set up temporary color buffer (for blit)
RenderingUtils.ReAllocateIfNeeded(ref rtTempColor, colorDesc, name: "_TemporaryColorTexture");
// Set up custom color target buffer (to render objects into)
if (settings.colorTargetDestinationID != ""){
RenderingUtils.ReAllocateIfNeeded(ref rtCustomColor, colorDesc, name: settings.colorTargetDestinationID);
// colorDestinationID is blank, use camera target instead
rtCustomColor = renderingData.cameraData.renderer.cameraColorTargetHandle;
// Using camera's depth target (that way we can ZTest with scene objects still)
RTHandle rtCameraDepth = renderingData.cameraData.renderer.cameraDepthTargetHandle;
ConfigureTarget(rtCustomColor, rtCameraDepth);
ConfigureClear(ClearFlag.Color, new Color(0,0,0,0));
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) {
CommandBuffer cmd = CommandBufferPool.Get();
// Set up profiling scope for Profiler & Frame Debugger
using (new ProfilingScope(cmd, _profilingSampler)) {
// Command buffer shouldn't contain anything, but apparently need to
// execute so DrawRenderers call is put under profiling scope title correctly
// Draw Renderers to Render Target (set up in OnCameraSetup)
SortingCriteria sortingCriteria = renderingData.cameraData.defaultOpaqueSortFlags;
DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagsList, ref renderingData, sortingCriteria);
if (settings.overrideMaterial != null) {
drawingSettings.overrideMaterialPassIndex = settings.overrideMaterialPass;
drawingSettings.overrideMaterial = settings.overrideMaterial;
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
// Pass our custom target to shaders as a Global Texture reference
// In a Shader Graph, you'd obtain this as a Texture2D property with "Exposed" unticked
if (settings.colorTargetDestinationID != "")
cmd.SetGlobalTexture(settings.colorTargetDestinationID, rtCustomColor);
// Apply material (e.g. Fullscreen Graph) to camera
if (settings.blitMaterial != null) {
RTHandle camTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
if (camTarget != null && rtTempColor != null) {
Blitter.BlitCameraTexture(cmd, camTarget, rtTempColor, settings.blitMaterial, 0);
Blitter.BlitCameraTexture(cmd, rtTempColor, camTarget);
// Execute Command Buffer one last time here and release it
// (otherwise we get weird recursive list in Frame Debugger)
public override void OnCameraCleanup(CommandBuffer cmd) {}
// Cleanup Called by feature below
public void Dispose() {
if (settings.colorTargetDestinationID != "")
// Exposed Settings
public class Settings {
public bool showInSceneView = true;
public RenderPassEvent _event = RenderPassEvent.AfterRenderingOpaques;
[Header("Draw Renderers Settings")]
public LayerMask layerMask = 1;
public Material overrideMaterial;
public int overrideMaterialPass;
public string colorTargetDestinationID = "";
[Header("Blit Settings")]
public Material blitMaterial;
public Settings settings = new Settings();
// Feature Methods
private CustomRenderPass m_ScriptablePass;
public override void Create() {
m_ScriptablePass = new CustomRenderPass(settings, name);
m_ScriptablePass.renderPassEvent = settings._event;
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) {
CameraType cameraType = renderingData.cameraData.cameraType;
if (cameraType == CameraType.Preview) return; // Ignore feature for editor/inspector previews & asset thumbnails
if (!settings.showInSceneView && cameraType == CameraType.SceneView) return;
protected override void Dispose(bool disposing) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment