├── .gitignore ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── FastBlur.cs ├── FastBlur.cs.meta ├── Limworks.FastBlur.asmdef └── Limworks.FastBlur.asmdef.meta ├── Shaders.meta ├── Shaders ├── FastBlur.shader └── FastBlur.shader.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-FastBlur-URP 2 | 3 | Fast Kawase blur for Unity URP 2021 LTS 4 | 5 | Blurred Texture is stored as `_CameraBlurTexture` 6 | 7 | Standard Mode takes 0.25 - 1.5 ms to render on RTX 3070 at 4k. 8 | 9 | Perfect use for blurred UI, fullscreen blurs, post processing, blurred glass, etc. 10 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e4f104a46a9c494d96c5a10195c7f93 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eb16eda7b69133446bf041ff27ef9b50 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/FastBlur.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.Rendering; 6 | using UnityEngine.Rendering.Universal; 7 | using UnityEngine.UIElements; 8 | 9 | 10 | namespace Limworks.Rendering.FastBlur 11 | { 12 | public class FastBlur : ScriptableRendererFeature 13 | { 14 | public static FastBlur Instance { get; private set; } 15 | internal abstract class BlurPass : ScriptableRenderPass , IDisposable 16 | { 17 | public FastBlurSettings blurSettings { get; set; } 18 | public RenderTargetIdentifier colorSource { get; set; } 19 | 20 | public virtual void Dispose() 21 | { 22 | } 23 | } 24 | internal class BlurPassStandard : BlurPass 25 | { 26 | Material blurMat => blurSettings.BlurMat; 27 | 28 | 29 | RenderTargetHandle tempTexture; 30 | RenderTexture BlurTexture; 31 | 32 | int blurIterations => (int)blurSettings.Radius; 33 | RenderTextureDescriptor renderTextureDescriptor1; 34 | Resolution downScaledResolution; 35 | bool isScene = false; 36 | public BlurPassStandard(RenderTextureDescriptor renderTextureDescriptor, bool isScene = false) 37 | { 38 | Init(renderTextureDescriptor); 39 | this.isScene = isScene; 40 | if(isScene) 41 | tempTexture.Init("_tempTexture_scene"); 42 | else 43 | tempTexture.Init("_tempTexture"); 44 | } 45 | public override void Dispose() 46 | { 47 | if (BlurTexture != null) 48 | BlurTexture.Release(); 49 | } 50 | public void Init(RenderTextureDescriptor renderTextureDescriptor) 51 | { 52 | renderTextureDescriptor.mipCount = 0; 53 | renderTextureDescriptor.useMipMap = false; 54 | renderTextureDescriptor.depthBufferBits = 0; 55 | renderTextureDescriptor.colorFormat = RenderTextureFormat.RGB111110Float; 56 | renderTextureDescriptor1 = renderTextureDescriptor; 57 | 58 | Dispose(); 59 | 60 | const float baseMpx = 1920 * 1080; 61 | const float maxMpx = 8294400; 62 | float mpx = renderTextureDescriptor.width * renderTextureDescriptor.height; 63 | float t = Mathf.InverseLerp(baseMpx, maxMpx, mpx); 64 | t = Mathf.Clamp01(t); 65 | float scale = Mathf.Lerp(1, 0.5f, t); 66 | downScaledResolution = new Resolution(); 67 | downScaledResolution.height = Mathf.FloorToInt(renderTextureDescriptor.height * scale); 68 | downScaledResolution.width = Mathf.FloorToInt(renderTextureDescriptor.width * scale); 69 | renderTextureDescriptor.width = downScaledResolution.width; 70 | renderTextureDescriptor.height = downScaledResolution.height; 71 | BlurTexture = new RenderTexture(renderTextureDescriptor.width, renderTextureDescriptor.height, 0, renderTextureDescriptor.colorFormat, 0); 72 | BlurTexture.filterMode = FilterMode.Bilinear; 73 | BlurTexture.name = isScene ? "_CameraBlurTexture_scene" : "_CameraBlurTexture"; 74 | } 75 | public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) 76 | { 77 | if(renderingData.cameraData.cameraTargetDescriptor.width != renderTextureDescriptor1.width 78 | || renderingData.cameraData.cameraTargetDescriptor.height != renderTextureDescriptor1.height) 79 | { 80 | Init(renderingData.cameraData.cameraTargetDescriptor); 81 | } 82 | if(BlurTexture == null) 83 | { 84 | Init(renderingData.cameraData.cameraTargetDescriptor); 85 | } 86 | Shader.SetGlobalTexture(BlurTexture.name, BlurTexture); 87 | } 88 | public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) 89 | { 90 | var cmd = CommandBufferPool.Get("Fast Blur"); 91 | cmd.GetTemporaryRT(tempTexture.id, downScaledResolution.width, downScaledResolution.height, 0, FilterMode.Bilinear, renderTextureDescriptor1.colorFormat); 92 | 93 | float offset = 0.5f; 94 | 95 | //first iteration of blur 96 | cmd.SetGlobalFloat("_Offset", offset); 97 | if(blurIterations == 1) 98 | { 99 | cmd.Blit(colorSource, BlurTexture, blurMat, 2); 100 | } 101 | else 102 | { 103 | cmd.Blit(colorSource, tempTexture.id, blurMat, 2); 104 | } 105 | 106 | offset = 0.5f; 107 | //rest of the iteration 108 | int iterationCount = 1; 109 | int maxIter = blurIterations; 110 | while (iterationCount < maxIter) 111 | { 112 | cmd.SetGlobalFloat("_Offset", offset + (float)iterationCount); 113 | cmd.Blit(tempTexture.id, BlurTexture, blurMat, 2); 114 | iterationCount++; 115 | 116 | if(iterationCount < blurIterations) 117 | { 118 | cmd.SetGlobalFloat("_Offset", offset + (float)iterationCount); 119 | cmd.Blit(BlurTexture, tempTexture.id, blurMat, 2); 120 | iterationCount++; 121 | 122 | if(iterationCount >= blurIterations) 123 | { 124 | cmd.Blit(tempTexture.id, BlurTexture); 125 | } 126 | } 127 | } 128 | 129 | if (blurSettings.ShowBlurredTexture) 130 | { 131 | cmd.Blit(BlurTexture, colorSource); 132 | } 133 | 134 | context.ExecuteCommandBuffer(cmd); 135 | cmd.Clear(); 136 | CommandBufferPool.Release(cmd); 137 | } 138 | public override void FrameCleanup(CommandBuffer cmd) 139 | { 140 | cmd.ReleaseTemporaryRT(tempTexture.id); 141 | } 142 | } 143 | private void Awake() 144 | { 145 | Instance = this; 146 | } 147 | void CreateMat() 148 | { 149 | var shader = Shader.Find("hidden/FastBlur"); 150 | if(shader == null) 151 | { 152 | Debug.LogWarning("Cannot find hidden/FastBlur shader!"); 153 | return; 154 | } 155 | 156 | Settings.BlurMat = CoreUtils.CreateEngineMaterial(shader); 157 | 158 | } 159 | BlurPass pass = null; 160 | #if UNITY_EDITOR 161 | BlurPass sceneview_pass = null; 162 | #endif 163 | public FastBlurSettings Settings = FastBlurSettings.Default; 164 | public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) 165 | { 166 | Settings.Radius = Mathf.Min(Mathf.Max(Settings.Radius, 1), 128); 167 | #if UNITY_EDITOR 168 | BlurPass currentPass = renderingData.cameraData.isSceneViewCamera ? sceneview_pass : pass; 169 | #else 170 | BlurPass currentPass = pass; 171 | #endif 172 | if (currentPass == null) 173 | { 174 | 175 | #if UNITY_EDITOR 176 | if (renderingData.cameraData.isSceneViewCamera) 177 | { 178 | currentPass = new BlurPassStandard(renderingData.cameraData.cameraTargetDescriptor, true); 179 | sceneview_pass = currentPass; 180 | } 181 | else 182 | { 183 | currentPass = new BlurPassStandard(renderingData.cameraData.cameraTargetDescriptor, false); 184 | pass = currentPass; 185 | } 186 | #else 187 | pass = currentPass; 188 | #endif 189 | CreateMat(); 190 | } 191 | currentPass.blurSettings = Settings; 192 | if (Settings.BlurMat == null) 193 | { 194 | CreateMat(); 195 | return; 196 | } 197 | currentPass.colorSource = renderer.cameraColorTarget; 198 | currentPass.renderPassEvent = (Settings.RenderQueue + Settings.QueueOffset); 199 | renderer.EnqueuePass(currentPass); 200 | } 201 | 202 | private void OnDestroy() 203 | { 204 | Dispose(true); 205 | } 206 | private void OnValidate() 207 | { 208 | Dispose(true); 209 | } 210 | protected override void Dispose(bool disposing) 211 | { 212 | base.Dispose(disposing); 213 | if(pass != null) 214 | pass.Dispose(); 215 | 216 | #if UNITY_EDITOR 217 | if (sceneview_pass != null) 218 | sceneview_pass.Dispose(); 219 | #endif 220 | } 221 | public override void Create() 222 | { 223 | pass = null; 224 | //nothing, creating pass on the fly 225 | } 226 | } 227 | [System.Serializable] 228 | public struct FastBlurSettings 229 | { 230 | [Tooltip("Approximate blur radius")] 231 | [Range(1, 128)] 232 | public int Radius; 233 | [Tooltip("When to do blurring")] 234 | public RenderPassEvent RenderQueue; 235 | [Tooltip("When to do blurring + offset")] 236 | public int QueueOffset; 237 | [Tooltip("Render blurred texture to screen")] 238 | public bool ShowBlurredTexture; 239 | internal Material BlurMat { get; set; } 240 | public static FastBlurSettings Default => new FastBlurSettings() 241 | { 242 | BlurMat = null, 243 | Radius = 32, 244 | RenderQueue = RenderPassEvent.AfterRenderingTransparents, 245 | QueueOffset = 0, 246 | ShowBlurredTexture = false, 247 | }; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /Runtime/FastBlur.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa0aa935175eed7428582e827dcae23d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Limworks.FastBlur.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Limworks.FastBlur", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:15fc0a57446b3144c949da3e2b9737a9", 6 | "GUID:df380645f10b7bc4b97d4f5eb6303d95" 7 | ], 8 | "includePlatforms": [], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /Runtime/Limworks.FastBlur.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 628f5e740b173f84d896bc6c01d998ff 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Shaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6f492594057758f4f909630acb98003b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Shaders/FastBlur.shader: -------------------------------------------------------------------------------- 1 | Shader "hidden/FastBlur" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | Tags { "RenderType"="Transparent" } 10 | ZWrite Off 11 | //veritcal 12 | Pass 13 | { 14 | CGPROGRAM 15 | #pragma vertex vert 16 | #pragma fragment frag 17 | #include "UnityCG.cginc" 18 | 19 | struct appdata 20 | { 21 | float4 vertex : POSITION; 22 | float2 uv : TEXCOORD0; 23 | }; 24 | 25 | struct v2f 26 | { 27 | float2 uv : TEXCOORD0; 28 | float4 vertex : SV_POSITION; 29 | }; 30 | 31 | sampler2D _MainTex; 32 | float4 _MainTex_ST; 33 | 34 | int PassIteration; 35 | float KernalSize; 36 | sampler2D _CameraBlurTexture; 37 | 38 | v2f vert (appdata v) 39 | { 40 | v2f o; 41 | o.vertex = UnityObjectToClipPos(v.vertex); 42 | o.uv = TRANSFORM_TEX(v.uv, _MainTex); 43 | return o; 44 | } 45 | float4 frag(v2f i) : SV_Target 46 | { 47 | float sy = (float)_ScreenParams.y / exp(PassIteration); 48 | float dy = 1 / sy; 49 | const int radius = 4; 50 | 51 | // sample the texture 52 | float4 col = float4(0, 0, 0, 0); 53 | 54 | float halfRadius = radius * 0.5f; 55 | 56 | [unroll] 57 | for (int ii = 0; ii < radius; ii++) 58 | { 59 | float id = ii - halfRadius; 60 | 61 | float4 t = float4(i.uv + float2(0, dy * id), 0, PassIteration); 62 | col += tex2Dlod(_MainTex, t); 63 | } 64 | 65 | col /= radius; 66 | 67 | return col; 68 | } 69 | ENDCG 70 | } 71 | 72 | //horizontal 73 | Pass 74 | { 75 | CGPROGRAM 76 | #pragma vertex vert 77 | #pragma fragment frag 78 | 79 | #include "UnityCG.cginc" 80 | 81 | struct appdata 82 | { 83 | float4 vertex : POSITION; 84 | float2 uv : TEXCOORD0; 85 | }; 86 | 87 | struct v2f 88 | { 89 | float2 uv : TEXCOORD0; 90 | float4 vertex : SV_POSITION; 91 | }; 92 | 93 | sampler2D _MainTex; 94 | float4 _MainTex_ST; 95 | 96 | sampler2D _CameraBlurTexture; 97 | int PassIteration; 98 | float KernalSize; 99 | 100 | v2f vert(appdata v) 101 | { 102 | v2f o; 103 | o.vertex = UnityObjectToClipPos(v.vertex); 104 | o.uv = TRANSFORM_TEX(v.uv, _MainTex); 105 | return o; 106 | } 107 | float4 frag(v2f i) : SV_Target 108 | { 109 | float sx = (float)_ScreenParams.x / exp(PassIteration); 110 | float dx = 1 / sx; 111 | 112 | // sample the texture 113 | float4 col = float4(0, 0, 0, 0); 114 | const int radius = 4; 115 | 116 | float halfRadius = radius * 0.5f; 117 | 118 | [unroll] 119 | for (int ii = 0; ii < radius; ii++) 120 | { 121 | float id = ii - halfRadius; 122 | float4 t = float4(i.uv + float2(dx * id, 0), 0, PassIteration); 123 | col += tex2Dlod(_MainTex, t); 124 | } 125 | 126 | col /= radius; 127 | 128 | return col; 129 | } 130 | ENDCG 131 | } 132 | 133 | //kawase 134 | Pass 135 | { 136 | CGPROGRAM 137 | #pragma vertex vert 138 | #pragma fragment frag 139 | 140 | #include "UnityCG.cginc" 141 | 142 | struct appdata 143 | { 144 | float4 vertex : POSITION; 145 | float2 uv : TEXCOORD0; 146 | }; 147 | 148 | struct v2f 149 | { 150 | float2 uv : TEXCOORD0; 151 | float4 vertex : SV_POSITION; 152 | }; 153 | 154 | sampler2D _MainTex; 155 | float4 _MainTex_ST; 156 | float4 _MainTex_TexelSize; 157 | float _Offset; 158 | 159 | v2f vert(appdata v) 160 | { 161 | v2f o; 162 | o.vertex = UnityObjectToClipPos(v.vertex); 163 | o.uv = TRANSFORM_TEX(v.uv, _MainTex); 164 | return o; 165 | } 166 | float4 frag(v2f i) : SV_Target 167 | { 168 | float off = _Offset; 169 | float2 res = _MainTex_TexelSize.xy; 170 | 171 | float4 col = float4(0, 0, 0, 0); 172 | col.rgb = tex2D(_MainTex, i.uv).rgb; 173 | 174 | col.rgb += tex2D(_MainTex, i.uv + float2(off, off) * res).rgb; 175 | col.rgb += tex2D(_MainTex, i.uv + float2(off, -off) * res).rgb; 176 | col.rgb += tex2D(_MainTex, i.uv + float2(-off, off) * res).rgb; 177 | col.rgb += tex2D(_MainTex, i.uv + float2(-off, -off) * res).rgb; 178 | col.rgb *= 0.2f; 179 | 180 | return col; 181 | } 182 | ENDCG 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Shaders/FastBlur.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 11aa4bf16803d6042b4ad0d8dcd4993c 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | preprocessorOverride: 0 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.limworks.fastblur", 3 | "version": "2.1.0", 4 | "displayName": "Unity URP Fast blur", 5 | "description": "Fast fullscreen blur render feature", 6 | "unity": "2021.3", 7 | "author":{ 8 | "name":"Joshua Lim", 9 | "url": "https://www.reddit.com/user/joshualim007/" 10 | } 11 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f86db7c4255ac1d4f9e888e8da8559af 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------