Skip to content

Instantly share code, notes, and snippets.

@forestrf
Forked from Refsa/BlurEffect.compute
Created April 8, 2022 19:46
Show Gist options
  • Select an option

  • Save forestrf/ccafa9556d0c1a8d0d964c7faf650c7b to your computer and use it in GitHub Desktop.

Select an option

Save forestrf/ccafa9556d0c1a8d0d964c7faf650c7b to your computer and use it in GitHub Desktop.
Unity URP custom render feature for UI Blur

A simple render feature and a compute shader to grab the scene color after rendering and downscaling + blurring it for use in UI.

![https://i.imgur.com/dZaOCcH.png]

Uses canvas stacking in order for this to work. The canvas that uses the blurring effect needs a lower sorting order than the other canvases in the scene. If it doesnt have a lower sorting order it will simply overwrite the other canvases.

Downscaling the grabbed scene color texture before blurring it is recommended for performance, but reduces the quality somewhat. Even a 2x downscale is a huge increase in performance, but tweak both that and the blur iterations to find a good middleground.

It's currently not working great with scene view, and you'll probably need a different method of approach for this to work nicely. Other approaches uses camera stacking and render textures to work, but that does require a bit more setup.

How To

  1. Add the "UIBlurFeature" to your renderer data
  2. Add the "BlurEffect.compute" to the feature settings "Effect Compute"
  3. A texture named "_UIBlurTexture" is now exposed to your shaders
  4. Add a secondary canvas and set "Sort Order" to be lower than all your other canvases, if one of your canvases dissappear you need a lower sort order
  5. Use the attached UIBlur shader on a RawImage in the new canvas you created
  6. Anything in the scene under the RawImage should now be blurred, including transparent objects

Issues

#pragma kernel Downscale
#pragma kernel GaussianBlurVertical
#pragma kernel GaussianBlurHorizontal
#pragma kernel BoxBlur
Texture2D<float4> _Source;
RWTexture2D<float4> _Dest;
float _Amount;
float2 _Size;
[numthreads(32,32,1)]
void Downscale(uint3 id : SV_DispatchThreadID)
{
static const float samples = rcp(_Amount * _Amount);
const int2 uv = id.xy * _Amount;
float4 result = 0;
for (int x = 0; x < _Amount; x++)
{
for (int y = 0; y < _Amount; y++)
{
result += _Source[uv + int2(x, y)];
}
}
_Dest[id.xy] = result * samples;
}
static const float gaussian_offset[3] = {0.0, 1.3846153846, 3.2307692308};
static const float gaussian_weight[3] = {0.2270270270, 0.3162162162, 0.0702702703};
[numthreads(32,32,1)]
void GaussianBlurVertical(uint3 id : SV_DispatchThreadID)
{
[branch] if (id.x >= _Size.x || id.y >= _Size.y) return;
float4 result = _Dest[id.xy] * gaussian_weight[0];
[unroll] for (int i = 1; i < 3; i++)
{
int2 uv = id.xy + int2(0, gaussian_offset[i]);
if (uv.y < _Size.y)
{
result += _Dest[uv] * gaussian_weight[i];
}
else
{
result += _Dest[id.xy] * gaussian_weight[i];
}
uv = id.xy - int2(0, gaussian_offset[i]);
if (uv.y > 0)
{
result += _Dest[uv] * gaussian_weight[i];
}
else
{
result += _Dest[id.xy] * gaussian_weight[i];
}
}
_Dest[id.xy] = result;
}
[numthreads(32,32,1)]
void GaussianBlurHorizontal(uint3 id : SV_DispatchThreadID)
{
[branch] if (id.x >= _Size.x || id.y >= _Size.y) return;
float4 result = _Dest[id.xy] * gaussian_weight[0];
[unroll] for (int i = 1; i < 3; i++)
{
int2 uv = id.xy + int2(gaussian_offset[i], 0);
if (uv.x < _Size.x)
{
result += _Dest[uv] * gaussian_weight[i];
}
else
{
result += _Dest[id.xy] * gaussian_weight[i];
}
uv = id.xy - int2(gaussian_offset[i], 0);
if (uv.x > 0)
{
result += _Dest[uv] * gaussian_weight[i];
}
else
{
result += _Dest[id.xy] * gaussian_weight[i];
}
}
_Dest[id.xy] = result;
}
[numthreads(32,32,1)]
void BoxBlur(uint3 id : SV_DispatchThreadID)
{
[branch] if (id.x >= _Size.x || id.y >= _Size.y) return;
float4 result = 0;
float count = 0;
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
const int2 uv = id.xy + int2(x, y);
if (uv.x < 0 || uv.y < 0 || uv.x >= _Size.x || uv.y >= _Size.y) continue;
result += _Dest[uv];
count++;
}
}
_Dest[id.xy] = result * rcp(count);
}
Shader "Unlit/UIBlur"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_Width ("Width of Blur", Range(0, 8)) = 4
}
SubShader
{
Tags
{
"RenderType"="Transparent"
}
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
Lighting Off
ZWrite Off
ZTest On
HLSLINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 screenPos : TEXCOORD1;
};
float _Width;
sampler2D _UIBlurTexture;
float4 _UIBlurTexture_TexelSize;
v2f vert(appdata v)
{
v2f o = (v2f)0;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
// Note: This is a really inefficient way of blurring
// We only use it to smooth out our downscaled texture
float4 BoxFilter(float2 screenUV)
{
const int width = _Width;
float4 result = 0;
int count = 0;
for (int x = -width; x <= width; x++)
{
for (int y = -width; y <= width; y++)
{
result += tex2D(_UIBlurTexture, screenUV + float2(x, y) * _UIBlurTexture_TexelSize.xy);
count++;
}
}
return result / count;
}
float4 frag(v2f i) : SV_Target
{
return BoxFilter(i.screenPos.xy / i.screenPos.w);
}
ENDHLSL
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDHLSL
}
}
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class UIBlurFeature : ScriptableRendererFeature
{
public enum BlurType
{
Gaussian,
Box,
}
const string TextureName = "_UIBlurTexture";
static int DownscaleKernelID = -1;
static int GaussianBlurKernelID = -1;
static int BoxBlurKernelID = -1;
[System.Serializable]
public class Settings
{
[Range(1, 16)] public int DownscaleAmount = 2;
public BlurType BlurType;
[Range(0, 16)] public int BlurIterations = 4;
public ComputeShader EffectCompute;
}
class BlurSceneColorPass : ScriptableRenderPass
{
RenderTargetHandle tempColorTarget;
Settings settings;
RenderTargetIdentifier cameraTarget;
Vector2Int scale;
Vector2Int groupSizes;
public BlurSceneColorPass(Settings s)
{
settings = s;
renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
tempColorTarget.Init(TextureName);
}
public void Setup(RenderTargetIdentifier cameraTarget)
{
this.cameraTarget = cameraTarget;
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
var downscaleDesc = cameraTextureDescriptor;
downscaleDesc.width = (int) ((float) downscaleDesc.width / (float) settings.DownscaleAmount);
downscaleDesc.height = (int) ((float) downscaleDesc.height / (float) settings.DownscaleAmount);
downscaleDesc.enableRandomWrite = true;
scale = new Vector2Int(downscaleDesc.width, downscaleDesc.height);
groupSizes = new Vector2Int(Mathf.CeilToInt(scale.x / 32f), Mathf.CeilToInt(scale.y / 32f));
cmd.GetTemporaryRT(tempColorTarget.id, downscaleDesc);
cmd.SetGlobalTexture(TextureName, tempColorTarget.Identifier());
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
// Shared parameters between compute kernels
cmd.SetComputeVectorParam(settings.EffectCompute, "_Size", new Vector2(scale.x, scale.y));
cmd.SetComputeFloatParam(settings.EffectCompute, "_Amount", settings.DownscaleAmount);
// Blit/Downscale scene color texture
{
cmd.SetComputeTextureParam(settings.EffectCompute, DownscaleKernelID, "_Source", cameraTarget);
cmd.SetComputeTextureParam(settings.EffectCompute, DownscaleKernelID, "_Dest",
tempColorTarget.Identifier());
cmd.DispatchCompute(settings.EffectCompute, DownscaleKernelID, groupSizes.x, groupSizes.y, 1);
}
// Apply iterative blur
{
int blurKernelID = settings.BlurType switch
{
BlurType.Box => BoxBlurKernelID,
BlurType.Gaussian => GaussianBlurKernelID,
};
cmd.SetComputeTextureParam(settings.EffectCompute, blurKernelID, "_Dest", tempColorTarget.Identifier());
cmd.SetComputeTextureParam(settings.EffectCompute, blurKernelID + 1, "_Dest", tempColorTarget.Identifier());
for (int i = 0; i < settings.BlurIterations; i++)
{
cmd.DispatchCompute(settings.EffectCompute, blurKernelID, groupSizes.x, groupSizes.y, 1);
if (settings.BlurType == BlurType.Gaussian)
{
cmd.DispatchCompute(settings.EffectCompute, blurKernelID + 1, groupSizes.x, groupSizes.y, 1);
}
}
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(tempColorTarget.id);
}
}
BlurSceneColorPass blurSceneColorPass;
[SerializeField] Settings settings;
public override void Create()
{
blurSceneColorPass = new BlurSceneColorPass(settings);
FindKernels();
}
void FindKernels()
{
if (settings.EffectCompute != null)
{
DownscaleKernelID = settings.EffectCompute.FindKernel("Downscale");
GaussianBlurKernelID = settings.EffectCompute.FindKernel("GaussianBlurVertical");
BoxBlurKernelID = settings.EffectCompute.FindKernel("BoxBlur");
}
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.EffectCompute == null)
{
return;
}
if (DownscaleKernelID == -1 || GaussianBlurKernelID == -1)
{
FindKernels();
}
blurSceneColorPass.Setup(renderer.cameraColorTarget);
renderer.EnqueuePass(blurSceneColorPass);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment