Skip to content

Instantly share code, notes, and snippets.

@Refsa
Last active September 25, 2025 22:42
Show Gist options
  • Select an option

  • Save Refsa/b3bde4bea0c920948f03756967a32cd7 to your computer and use it in GitHub Desktop.

Select an option

Save Refsa/b3bde4bea0c920948f03756967a32cd7 to your computer and use it in GitHub Desktop.
Grab Screen Feature for URP 2.0

It's a bit broken and only works for AfterRendering or before BeforeRenderingPostProcessing for now. Only tested on URP 11 with Unity 2021.1.24f1.

Material in Settings is created from "GrabPassBlit.shader".
This is a bit more expensive in both memory and compute for now, so I might come back when I have time to dig more into it.

Shader "Hidden/GrabPassBlit"
{
Properties { }
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Name "FinalBlit"
ZWrite Off ZTest Always Blend Off Cull Off
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 screen_pos : TEXCOORD0;
};
TEXTURE2D(_GSF_RenderPass);
SAMPLER(sampler_GSF_RenderPass);
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.screen_pos = ComputeScreenPos(o.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float2 uv = i.screen_pos.xy / i.screen_pos.w;
float4 col = SAMPLE_TEXTURE2D(_GSF_RenderPass, sampler_GSF_RenderPass, uv);
return float4(col.rgb, length(col.rgb) != 0);
}
ENDHLSL
}
Pass
{
Name "DepthBuffer"
ZTest Always ZWrite On
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 screen_pos : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o = (v2f)0;
o.vertex = TransformObjectToHClip(v.vertex);
o.screen_pos = ComputeScreenPos(o.vertex);
return o;
}
float4 frag(v2f i, out float depth : SV_DEPTH) : SV_TARGET0
{
depth = SampleSceneDepth(i.screen_pos.xy / i.screen_pos.w);
return depth;
}
ENDHLSL
}
Pass
{
Name "AfterPP"
ZWrite Off ZTest Always Blend Off Cull Off
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 screen_pos : TEXCOORD0;
};
TEXTURE2D(_AfterPostProcessTexture);
SAMPLER(sampler_AfterPostProcessTexture);
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.screen_pos = ComputeScreenPos(o.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float2 uv = i.screen_pos.xy / i.screen_pos.w;
return SAMPLE_TEXTURE2D(_AfterPostProcessTexture, sampler_AfterPostProcessTexture, uv);
}
ENDHLSL
}
}
}
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.Universal.Internal;
public class GrabScreenFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public string TextureName = "_GrabPassTransparent";
public LayerMask LayerMask;
public RenderPassEvent RenderPassEvent;
public int Offset;
public Material BlitMaterial;
}
class GrabPass : ScriptableRenderPass
{
RenderTargetHandle tempColorTarget;
Settings settings;
public GrabPass(Settings s)
{
settings = s;
renderPassEvent = settings.RenderPassEvent + settings.Offset;
tempColorTarget.Init(settings.TextureName);
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
var descriptor = cameraTextureDescriptor;
descriptor.msaaSamples = 1;
descriptor.depthBufferBits = 0;
cmd.GetTemporaryRT(tempColorTarget.id, descriptor);
cmd.SetGlobalTexture(settings.TextureName, tempColorTarget.Identifier());
ConfigureTarget(tempColorTarget.Identifier());
ConfigureClear(ClearFlag.Color, Color.black);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
if ((int)settings.RenderPassEvent >= (int)RenderPassEvent.BeforeRenderingPostProcessing)
{
cmd.Blit(null, tempColorTarget.Identifier(), settings.BlitMaterial, 2);
}
else
{
var cameraTarget = renderingData.cameraData.renderer.cameraColorTarget;
Blit(cmd, cameraTarget, tempColorTarget.Identifier());
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(tempColorTarget.id);
}
}
class RenderPass : ScriptableRenderPass
{
Settings settings;
List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
FilteringSettings m_FilteringSettings;
RenderStateBlock m_RenderStateBlock;
RenderTargetHandle renderTarget;
RenderTargetIdentifier depthHandle;
public RenderTargetIdentifier RenderTarget => renderTarget.Identifier();
public RenderPass(Settings settings)
{
this.settings = settings;
renderPassEvent = settings.RenderPassEvent + settings.Offset + 1;
m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForwardOnly"));
m_FilteringSettings = new FilteringSettings(RenderQueueRange.all, settings.LayerMask);
m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
renderTarget.Init("_GSF_RenderPass");
}
public void Setup(RenderTargetIdentifier depthHandle)
{
this.depthHandle = depthHandle;
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
var desc = renderingData.cameraData.cameraTargetDescriptor;
desc.msaaSamples = 1;
cmd.GetTemporaryRT(renderTarget.id, desc, FilterMode.Point);
if ((int)settings.RenderPassEvent >= (int)RenderPassEvent.BeforeRenderingPostProcessing)
{
ConfigureTarget(renderTarget.Identifier(), depthHandle);
ConfigureClear(ClearFlag.Color, Color.clear);
}
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
DrawingSettings drawSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonOpaque);
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings, ref m_RenderStateBlock);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(renderTarget.id);
}
}
class CopyDepthPass : ScriptableRenderPass
{
RenderTargetHandle depthHandle;
Settings settings;
public RenderTargetIdentifier DepthHandle => depthHandle.Identifier();
public CopyDepthPass(Settings settings)
{
this.settings = settings;
depthHandle.Init("_DepthCopy");
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
var desc = renderingData.cameraData.cameraTargetDescriptor;
desc.msaaSamples = 1;
cmd.GetTemporaryRT(depthHandle.id, desc, FilterMode.Point);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
var cmd = CommandBufferPool.Get("OutlineBlitCMD");
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
cmd.Blit(depthHandle.Identifier(), depthHandle.Identifier(), settings.BlitMaterial, 1);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(depthHandle.id);
}
}
class BlitPass : ScriptableRenderPass
{
Settings settings;
RenderTargetIdentifier renderTarget;
public BlitPass(Settings settings)
{
this.settings = settings;
renderPassEvent = settings.RenderPassEvent + settings.Offset + 2;
}
public void Setup(RenderTargetIdentifier renderTarget)
{
this.renderTarget = renderTarget;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
var screenTex = colorAttachment;
cmd.Blit(null, screenTex, settings.BlitMaterial, 0);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
GrabPass grabPass;
RenderPass renderPass;
BlitPass blitPass;
CopyDepthPass copyDepthPass;
[SerializeField] Settings settings = new Settings();
public override void Create()
{
grabPass = new GrabPass(settings);
renderPass = new RenderPass(settings);
blitPass = new BlitPass(settings);
copyDepthPass = new CopyDepthPass(settings);
copyDepthPass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if ((int)settings.RenderPassEvent >= (int)RenderPassEvent.BeforeRenderingPostProcessing)
{
renderer.EnqueuePass(copyDepthPass);
}
renderer.EnqueuePass(grabPass);
renderPass.Setup(copyDepthPass.DepthHandle);
renderer.EnqueuePass(renderPass);
if ((int)settings.RenderPassEvent >= (int)RenderPassEvent.BeforeRenderingPostProcessing)
{
blitPass.Setup(renderPass.RenderTarget);
renderer.EnqueuePass(blitPass);
}
}
}
@DanielBird
Copy link

Thank you for making this publicly available!
In Unity 2022.2 with URP 14.0.5 this works for me.

@nullsoftware
Copy link

nullsoftware commented Sep 25, 2025

Hi,
I made my own version about a year ago. It's also buggy, but it works on Unity 2023.2

Current issues:

  • Doesn't properly display in the editor. Transparent parts appear black.
  • Doesn't support post-processing effects, like Depth of Field.

If anyone knows how to fix this I would be very grateful!

Here the CameraColorFeature.cs

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

public class CustomCameraColorPassFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        public LayerMask layerMask = -1;
        public Material overrideMaterial = null;
    }

    public Settings settings = new Settings();
    private GrabCameraColorPass grabPass;
    private CustomCameraColorPass customPass;

    public override void Create()
    {
        grabPass = new GrabCameraColorPass(settings);
        customPass = new CustomCameraColorPass(settings);
    }

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

    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
        grabPass.Setup(renderer.cameraColorTargetHandle);
    }

    class GrabCameraColorPass : ScriptableRenderPass
    {
        private Settings settings;
        private RTHandle cameraColorHandle;
        private FilteringSettings filteringSettings;
        private static readonly ShaderTagId shaderTag = new ShaderTagId("CustomCameraColorPass");
        private RenderTexture _colorTex;

        public GrabCameraColorPass(Settings settings)
        {
            this.settings = settings;
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
        }

        public void Setup(RTHandle colorHandle)
        {
            //this.cameraColorHandle = colorHandle;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            cameraTextureDescriptor.depthBufferBits = 0;
           
            //cameraTextureDescriptor.colorFormat = RenderTextureFormat.Default;
            cameraTextureDescriptor.enableRandomWrite = false;
            
            _colorTex = RenderTexture.GetTemporary(cameraTextureDescriptor);
            //cameraColorHandle = RTHandles.Alloc(cameraTextureDescriptor, name: "_CameraColorTexture");

            //cmd.GetTemporaryRT(cameraColorHandle.GetInstanceID(), cameraTextureDescriptor, FilterMode.Bilinear);
            cmd.SetGlobalTexture("_CameraColorTexture", _colorTex);
            //ConfigureTarget(cameraColorHandle);
            //ConfigureClear(ClearFlag.None, Color.clear); // Don't clear the target
        }

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

            // Set up sorting and drawing settings
            var sortingSettings = new SortingSettings(renderingData.cameraData.camera)
            {
                criteria = SortingCriteria.SortingLayer // Adjust as needed
                
            };

            var drawingSettings = new DrawingSettings(shaderTag, sortingSettings)
            {
                overrideMaterial = settings.overrideMaterial, // Optional for debugging
                 enableInstancing = false,
                
            };

            cmd.CopyTexture(renderingData.cameraData.renderer.cameraColorTargetHandle, _colorTex);
            //cmd.SetGlobalTexture("_CameraColorTexture", _colorTex);
            // Draw renderers with the custom shader pass
            // context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);


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

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);*/

            CommandBuffer cmd = CommandBufferPool.Get();
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();

            cmd.CopyTexture(renderingData.cameraData.renderer.cameraColorTargetHandle, _colorTex);
            //cmd.Blit(renderingData.cameraData.renderer.cameraColorTargetHandle, _colorTex);

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

        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            if (_colorTex)
            {
                RenderTexture.ReleaseTemporary(_colorTex);
                _colorTex = null;
            }
            //cmd.ReleaseTemporaryRT(cameraColorHandle.GetInstanceID());
            // No cleanup needed
        }
    }

    class CustomCameraColorPass : ScriptableRenderPass
    {
        private Settings settings;
        private FilteringSettings filteringSettings;
        private RenderStateBlock renderStateBlock;
        private static readonly ShaderTagId shaderTag = new ShaderTagId("CustomCameraColorPass");

        public CustomCameraColorPass(Settings settings)
        {
            this.settings = settings;
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing + 1;
            filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.layerMask);
            renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
        }


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

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

            DrawingSettings drawingSettings = CreateDrawingSettings(shaderTag, ref renderingData, SortingCriteria.CommonTransparent);
            RendererListParams rendererListParams = new RendererListParams(renderingData.cullResults, drawingSettings, filteringSettings);
            RendererList rendererList = context.CreateRendererList(ref rendererListParams);
            cmd.DrawRendererList(rendererList);

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

    }
}

Shader Refract_Simplified.shader

Shader "Examples/Refract_Simplified"
{
    Properties
    {
        _BaseColor ("Color", Color) = (1,1,1,1)
        _RefractionPower ("Refraction Power", Range(0, 0.1)) = 0.05
        _RefractionBlending ("Refraction Blending", Range(0, 1)) = 0.4
    }

    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent+1000" }
        LOD 200

        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        Cull Back

        Pass
        {
            Name "CustomCameraColorPass"
            Tags { "LightMode"="CustomCameraColorPass" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _ADDITIONAL_LIGHTS
            #pragma multi_compile_fragment _ _SHADOWS_SOFT

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
                float4 screenPos : TEXCOORD2;
            };

            TEXTURE2D(_CameraColorTexture);
            SAMPLER(sampler_CameraColorTexture);


            CBUFFER_START(UnityPerMaterial)
                float4 _BaseColor;
                float _RefractionPower;
                float _RefractionBlending;
            CBUFFER_END

            Varyings vert(Attributes input)
            {
                Varyings output;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS);

                output.positionCS = vertexInput.positionCS;
                output.normalWS = normalInput.normalWS;
                output.uv = input.uv;
                output.screenPos = ComputeScreenPos(output.positionCS);

                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                half4 albedo = _BaseColor;
                
                half3 normalWS = normalize(input.normalWS);
                
                float2 screenUV = input.screenPos.xy / input.screenPos.w;
                
                float2 refractionOffset = normalWS.xy * _RefractionPower;  
                screenUV += refractionOffset;
                
                // Sample the background (refracted) color
                float4 refractedColor = SAMPLE_TEXTURE2D(_CameraColorTexture, sampler_CameraColorTexture, screenUV);
                refractedColor.a = 1;
                
                #ifdef _OVERRIDE_ALPHA
                refractedColor.xyz = lerp(refractedColor.xyz, albedo.rgb, _RefractionBlending);
                #else
                refractedColor.xyz = lerp(refractedColor.xyz, albedo.rgb, albedo.a * _RefractionBlending);
                #endif

                return refractedColor;
            }
            ENDHLSL
        }

    }

    Fallback "Universal Render Pipeline/Lit"
}

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