Hi, I have a vertex-fragment shader that use the Unity3D GrabPass functionality (it grabs the screen). And I apply my GrabPass to have a transparent effect. Code (csharp): GrabPass { "_GrabTex" } sampler2D _GrabTex; // float4 grabPassPos : TEXCOORD4 is used to apply the grabbed texture // in the right place and is calculated in the vertex half4 transparent = tex2Dproj(_GrabTex, UNITY_PROJ_COORD(IN.grabPassPos)); half4 baseColor = transparent; return baseColor; I am searching a simple way of blurring my grabbed texture. I don't know how to apply my blur because it's a tex2Dproj and I don't know where to apply it. I am searching for a one pass optimized simple blur but I don't know if it's possible. I know a bit about Gaussian and Box blur which seems to be the simplest but how can I apply them in a tex2Dproj ? I know this package but it seems to be controversial and not very optimized. Thanks a lot !
Dont use grabpass, use a custom rendertexture and the simplest/lousiest way to blur it is to reduce the size of the texture after rendering to it. Or there are also alternative ways as you mentioned. The main point is, if you want control on things, then dont use grabpass.
Thanks for your answer Aubergine ! It is for a water transparency effect how can I use something other than GrabPass ? For me GrabPass is more optimized than a camera with a renderTexture but maybe I am wrong. I can't apply some blur on the grabbed texture ?
grabpass is a render texture itself that is rendered internally. You can do the blur with grabpass too, but if you want more control, render it yourself.
For the moment I want to apply blur on my grabpass texture. I saw that I can do it like this : Code (csharp): float3 proj = UNITY_PROJ_COORD(grabWithOffset); half4 top = tex2Dproj(_GrabTex, proj + float3(0, -_Blur, 0)); half4 bot = tex2Dproj(_GrabTex, proj + float3(0, _Blur, 0)); half4 left = tex2Dproj(_GrabTex, proj + float3(-_Blur, 0, 0)); half4 right = tex2Dproj(_GrabTex, proj + float3(_Blur,0, 0)); half4 center = tex2Dproj(_GrabTex, proj ); return ((top + bot + left + right + center + center) / 6.0f); But this is hardcoded. And the result is a very simple blur not smoothed. I know that there is a concept of kernel but I don't understand it. If you set the value in the kernel which seems to be an array how can you apply it in your texture. And I saw that this kernel can be used in a loop, but in unity shader there is no access to for or while loops.
Grabpass is different from a rendertexture in that it doesn't require to re-render the scene. It blits the contents of the framebuffer to a temporary texture, which isn't free but still much cheaper than rendering the scene multiple times. Sure, you have more control with render to texture approaches, but for simple stuff grabpass should be fine. I slapped toghether the refraction shader with gaussian blur. It's a bit hackish, but gets the job done. The kernel is hard coded for efficiency. If you know an efficient way for a dynamic kernel, let me know Depending on geometry it could be more efficient to do a single pass with two dimensional gaussian, at least with a small kernel. Not sure though. I did it splitted in horizontal+vertical as everyone else and I'm too lazy to try. Third pass is just for distortion and tint color. Code (csharp): Shader "Custom/SimpleGrabPassBlur" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _BumpAmt ("Distortion", Range (0,128)) = 10 _MainTex ("Tint Color (RGB)", 2D) = "white" {} _BumpMap ("Normalmap", 2D) = "bump" {} _Size ("Size", Range(0, 20)) = 1 } Category { // We must be transparent, so other objects are drawn before this one. Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque" } SubShader { // Horizontal blur GrabPass { Tags { "LightMode" = "Always" } } Pass { Tags { "LightMode" = "Always" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; }; v2f vert (appdata_t v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; return o; } sampler2D _GrabTexture; float4 _GrabTexture_TexelSize; float _Size; half4 frag( v2f i ) : COLOR { // half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); // return col; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernelx) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x * kernelx*_Size, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum; } ENDCG } // Vertical blur GrabPass { Tags { "LightMode" = "Always" } } Pass { Tags { "LightMode" = "Always" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; }; v2f vert (appdata_t v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; return o; } sampler2D _GrabTexture; float4 _GrabTexture_TexelSize; float _Size; half4 frag( v2f i ) : COLOR { // half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); // return col; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernely) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y * kernely*_Size, i.uvgrab.z, i.uvgrab.w))) * weight //G(X) = (1/(sqrt(2*PI*deviation*deviation))) * exp(-(x*x / (2*deviation*deviation))) sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum; } ENDCG } // Distortion GrabPass { Tags { "LightMode" = "Always" } } Pass { Tags { "LightMode" = "Always" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; float2 uvbump : TEXCOORD1; float2 uvmain : TEXCOORD2; }; float _BumpAmt; float4 _BumpMap_ST; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap ); o.uvmain = TRANSFORM_TEX( v.texcoord, _MainTex ); return o; } fixed4 _Color; sampler2D _GrabTexture; float4 _GrabTexture_TexelSize; sampler2D _BumpMap; sampler2D _MainTex; half4 frag( v2f i ) : COLOR { // calculate perturbed coordinates half2 bump = UnpackNormal(tex2D( _BumpMap, i.uvbump )).rg; // we could optimize this by just reading the x y without reconstructing the Z float2 offset = bump * _BumpAmt * _GrabTexture_TexelSize.xy; i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy; half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); half4 tint = tex2D( _MainTex, i.uvmain ) * _Color; return col * tint; } ENDCG } } } } Here's how it looks. Cheers.
Thanks a lot Cician ! I will look at your shader. I've found an interesting optimization of gaussian blur. But I don't know if it's really convincing. I don't understand why the number of pass should depend on the geometry. Why do separate the refraction pass ? I thought that the more pass you have the less it's optimized. What are the interest of having multiple pass (I know that it's of topic but I am just asking) A strange but normal behaviour appended on zoom, I can see the duplicate of the texture, is there a way to avoid that ?
The shader in fact is not optimized at all. Most people optimize gaussian blur by splitting it into horizontal and vertical pass, but in this case mesh geometry is rendered for each pass, which may outweight the benefits depending on multiple factors, such as triangle count and kernel size. Its's first time I'm doing the blur thingy and I'm just guessing this is the case, so take with a grain of salt. As for diffraction pass, it's just simpler this way. In fact the second and third passes can be combined into one. I don't understand. Screenshot or it didn't happen. Just a guess: try Z-Priming. NB: usual gotchas apply... Transparency issues: popping and no shadows. Grabpass: foreground objects bleeding in the distortion (avoidable with a mask, see gpu gems) and artifacts near screen edges.
Here is a screenshot of what I think it's strange but "normal". When I zoom with the camera. Where does it come from ? For me it depends of the kernel size and the smooth of the blur.
Is there a way to avoid this effect ? Is there a solution to have an efficient simple blur effect. It seems that every blur is based on the duplication of the image then blending.
That's exactly how blur is done cheaply. by taking a picture, blowing it up and showing it again. More extensive forms of blur would become much more heavy (And i wouldn't be sure how that's computed either).
I can't seem to get that example to work, throws a ton of errors. something about the shader not having certain properties, while those properties certainly seem to be there. I am still very much learning how shaders work, so it all seems very complex to me. I was having a look at the outline toon shader in unity to make something like a glow shader, but I can't seem to figure out how I'd blur the outline =/ Maybe that shader is not the best thing to use as a base for it.
This doesn't work well on mobile but I managed to get rid of the artifacts up to ~30 pixel strength. The edges will suffer on that level of strength though. Gaussian Blur This is a 10 pixel (gap) Gaussian blur.
Just dropping a note. Food for thaught. With command buffers one could avoid some costly memory copies of grab pass. At least between two passes of separable blur. See the first example here: http://blogs.unity3d.com/2015/02/06/extending-unity-5-rendering-pipeline-command-buffers/
Hi Cician, Have you updated your shader using the above approach Unity's new CB. Please post your new shader here, if So..
No. Sorry, but I'm currently mostly working in Unreal Engine. I did try the provided example though. It works (albeit not correctly in editor viewport, correctly at runtime) and it uses a separable blur. Didn't try anything beyond that.
Yup, I tried this too. But, It is not performance friendly... Any way, Thank you Cician for sharing the shader here, I really appreciate that..
Reviving this thread as I'm trying to do small blurs like @flyingbanana using the Command Buffers approach example Aras posted. Unfortunately even on the latest iOS devices and rendering a small portion of the screen the framerate plummets. Has anyone had success during these sorts of blurs with Command Buffers on mobile?
I think deferred would be your problem on mobile long before command buffers got involved... then there's grabpass and all sorts of expensive stuff.
I don't have much experience with mobile, but here's some food for thought. Fist I'd try what is the performance hit of just capturing the framebuffer or a part of it and redrawing it with a simple shader. If that's acceptably low then you can try optimizing the blur part. If you target modern devices with compute support then I suggest you read this: https://software.intel.com/en-us/bl...ast-real-time-gpu-based-image-blur-algorithms Otherwise you could maybe take advantage of the tiling nature of [most] mobile GPUs. Even without compute support the above article gives some insights. If you cannot afford a full gaussian blur, you can cheat with cheap[er] box blurs and find a performance-quality sweet spot.
So, I know this is an old thread, but I just needed some UI Blur for a project and cician's shader helped a lot. However, I ended up modifying it a bit so I can use the Image's alpha to mask the blur, so I'll just post the shader here in case anyone needs it in the future. Two notes however: 1) Because of the grab passes it is pretty much unusable on iOS (and maybe Android as well. I haven't tested). 2) I made to use with Unity's UI Image component. If you need it for something else, you can probably remove the [HideInInspector] property from the _MainTex. Code (CSharp): // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' // Based on cician's shader from: https://forum.unity3d.com/threads/simple-optimized-blur-shader.185327/#post-1267642 Shader "Custom/MaskedUIBlur" { Properties { _Size ("Blur", Range(0, 30)) = 1 [HideInInspector] _MainTex ("Tint Color (RGB)", 2D) = "white" {} } Category { // We must be transparent, so other objects are drawn before this one. Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque" } SubShader { // Horizontal blur GrabPass { "_HBlur" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; float2 uvmain : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; o.uvmain = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } sampler2D _HBlur; float4 _HBlur_TexelSize; float _Size; half4 frag( v2f i ) : COLOR { float alpha = tex2D(_MainTex, i.uvmain).a; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernelx) tex2Dproj( _HBlur, UNITY_PROJ_COORD(float4(i.uvgrab.x + _HBlur_TexelSize.x * kernelx * _Size * alpha, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum ; } ENDCG } // Vertical blur GrabPass { "_VBlur" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; float2 uvmain : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; o.uvmain = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } sampler2D _VBlur; float4 _VBlur_TexelSize; float _Size; half4 frag( v2f i ) : COLOR { float alpha = tex2D(_MainTex, i.uvmain).a; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernely) tex2Dproj( _VBlur, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _VBlur_TexelSize.y * kernely * _Size * alpha, i.uvgrab.z, i.uvgrab.w))) * weight sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum; } ENDCG } } } }
in case anyone comes across this, I modded vinipc's masked blur UI shader to add vertex color support, which means the UI Image's color property can affect the sprite now... here's my modified version: Code (CSharp): // Based on cician's shader from: https://forum.unity3d.com/threads/simple-optimized-blur-shader.185327/#post-1267642 // also from https://forum.unity3d.com/threads/simple-optimized-blur-shader.185327/ Shader "Custom/MaskedUIBlur" { Properties { _Size ("Blur", Range(0, 30)) = 1 [HideInInspector] _MainTex ("Texture (RGB)", 2D) = "white" {} // _TintColor ("Tint Color", Color) = (1,1,1,1) } Category { // We must be transparent, so other objects are drawn before this one. Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque" } SubShader { ZWrite Off // Horizontal blur GrabPass { "_HBlur" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; float4 color : COLOR; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; float2 uvmain : TEXCOORD1; float4 color : COLOR; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; o.uvmain = TRANSFORM_TEX(v.texcoord, _MainTex); o.color = v.color; return o; } sampler2D _HBlur; float4 _HBlur_TexelSize; float _Size; half4 frag( v2f i ) : COLOR { float alpha = tex2D(_MainTex, i.uvmain).a * i.color.a; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernelx) tex2Dproj( _HBlur, UNITY_PROJ_COORD(float4(i.uvgrab.x + _HBlur_TexelSize.x * kernelx * _Size * alpha, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum ; } ENDCG } // Vertical blur GrabPass { "_VBlur" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float2 texcoord: TEXCOORD0; float4 color : COLOR; }; struct v2f { float4 vertex : POSITION; float4 uvgrab : TEXCOORD0; float2 uvmain : TEXCOORD1; float4 color : COLOR; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); #if UNITY_UV_STARTS_AT_TOP float scale = -1.0; #else float scale = 1.0; #endif o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * scale) + o.vertex.w) * 0.5; o.uvgrab.zw = o.vertex.zw; o.uvmain = TRANSFORM_TEX(v.texcoord, _MainTex); o.color = v.color; return o; } sampler2D _VBlur; float4 _VBlur_TexelSize; float _Size; //fixed4 _TintColor; half4 frag( v2f i ) : COLOR { float alpha = tex2D(_MainTex, i.uvmain).a * i.color.a; half4 sum = half4(0,0,0,0); #define GRABPIXEL(weight,kernely) tex2Dproj( _VBlur, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _VBlur_TexelSize.y * kernely * _Size * alpha, i.uvgrab.z, i.uvgrab.w))) * weight sum += GRABPIXEL(0.05, -4.0); sum += GRABPIXEL(0.09, -3.0); sum += GRABPIXEL(0.12, -2.0); sum += GRABPIXEL(0.15, -1.0); sum += GRABPIXEL(0.18, 0.0); sum += GRABPIXEL(0.15, +1.0); sum += GRABPIXEL(0.12, +2.0); sum += GRABPIXEL(0.09, +3.0); sum += GRABPIXEL(0.05, +4.0); return sum + i.color * alpha; } ENDCG } } } }
Is there a way to get the blur shaders with grab passes optimized on mobile, or is that pretty much out of the question?
Hi, I have a problem with the shader. In my game I use dark themed UI, i.e. every image has black color(with different transparency). When I put "bright" colors (like red, white, yellow etc.) all is ok, but the shader just does not work with black color. It produces blur effect, but there is no tint color. What can you suggest?
You should do your entire ui in white, and tint it back down to dark on Unity's side. Because you can't make something brighter by tinting it but you can make it darker.
When I adjust TintColor in material's inspector nothing happens. The blur affected only by image's color.
I have fixed that, but again, when TintColor set to black and image color to white, it has no effect, blur still purely white.
This shader is not working when CANVAS render mode set to be World Space and CAMERA projection set to be Perspective. Please help
This shader can not be used as multiple items at the same time. For example, I used this shader for scrolling items and only one of the items worked properly. Is there another shade with the right quality that can be used in multiples?
in this article i explained about command buffer that you can use multiple effects https://www.linkedin.com/pulse/image-effect-specific-part-objects-seyed-morteza-kamali/
Hi, if anyone care, i'm refactor and basically make my own version of UI blur shader with advantages. May be used to make blury BG in pause menu with render texture. It is fully compatible with UI, has alpha clipping and more. In comparison with other variants, it's highly maintainable and has all hardcode gone. It is now truely weight-based, and you can easily create your own dispersion shape. No GrabPasses used, since they are expensive in terms of performance (https://docs.unity3d.com/Manual/SL-GrabPass.html), they are not compatible with URP, HDRP and SRP, not compatible with mobile and just hard to work with. Gist: https://gist.github.com/nilpunch/2060a180e36bf610171d0525375ba13f Here is it: Code (CSharp): Shader "UI/BlurImage" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) [Space(50)] _BlurX ("X Blur", Range(0.0, 0.5)) = 0.001 _BlurY ("Y Blur", Range(0.0, 0.5)) = 0.001 [Space] _Focus ("Focus", Range(0.0, 1.0)) = 0 _Distribution ("Distribution", Range(0.0, 1.0)) = 0.18 _Iterations ("Iterations", Integer) = 5 [Space(50)] _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float2 worldPosition : TEXCOORD1; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed _BlurX; fixed _BlurY; fixed _Focus; fixed _Distribution; int _Iterations; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; float4 tex2Dblur(float2 position, float2 offset) { const float2 blur_offset = position.xy + float2(_BlurX, _BlurY).xy * offset * (1 - _Focus); return tex2D(_MainTex, blur_offset) + _TextureSampleAdd; } float calculateWeight(float distance) { return lerp(1, _Distribution, distance); } v2f vert(appdata_t v) { v2f OUT; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = v.vertex; OUT.vertex = UnityObjectToClipPos(v.vertex); OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); OUT.color = v.color * _Color; return OUT; } fixed4 frag(v2f IN) : SV_Target { const int2 iterations = int2(_Iterations, _Iterations); const float centralPixelWeight = 1; float4 color_sum = float4(0,0,0,0); float weight_sum = 0; // Add central pixel color_sum += tex2Dblur(IN.texcoord, float2(0, 0)) * centralPixelWeight; weight_sum += centralPixelWeight; // Add central column for (int horizontal = 1; horizontal < iterations.x; ++horizontal) { const float offset = (float)horizontal / iterations.x; const float weight = calculateWeight(offset); color_sum += tex2Dblur(IN.texcoord, float2(offset, 0)) * weight; color_sum += tex2Dblur(IN.texcoord, float2(-offset, 0)) * weight; weight_sum += weight * 2; } // Add central row for (int vertical = 1; vertical < iterations.y; ++vertical) { const float offset = (float)vertical / iterations.y; const float weight = calculateWeight(offset); color_sum += tex2Dblur(IN.texcoord, float2(0, offset)) * weight; color_sum += tex2Dblur(IN.texcoord, float2(0, -offset)) * weight; weight_sum += weight * 2; } // Add quads for (int x = 1; x < iterations.x; ++x) { for (int y = 1; y < iterations.y; ++y) { float2 offset = float2((float)x / iterations.x, (float)y / iterations.y); const float offsetLength = length(offset); const float weight = calculateWeight(offsetLength); color_sum += tex2Dblur(IN.texcoord, float2(offset.x, offset.y)) * weight; color_sum += tex2Dblur(IN.texcoord, float2(-offset.x, offset.y)) * weight; color_sum += tex2Dblur(IN.texcoord, float2(-offset.x, -offset.y)) * weight; color_sum += tex2Dblur(IN.texcoord, float2(offset.x, -offset.y)) * weight; weight_sum += weight * 4; } } float4 final_color = color_sum / weight_sum * IN.color; #ifdef UNITY_UI_CLIP_RECT final_color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip(final_color.a - 0.001); #endif return final_color; } ENDCG } } }
I'm curious how to use this shader. I've created an image in my canvas, and added a material with this shader, but all I see is a blank white box.
After some experiments, i make it works. In my case i needed blur main canvas and show popups or loading spiner under it. I created 2 canvases and 2 cameras. First camera render main canvas that will blured, second camera render raw blured image and unblured content after that. So my hierarchy looks like this. Red arrows shows what canvas is rendere by camera. Spoiler: Hierarchy Camera setups: Spoiler: Main camera for main canvas Spoiler: Camera for canvas after blur Under [UI_AFTER_BLUR] canvas i created game object with Raw Image, its render RenderTexture. Note that you dont need set material on this Raw Image component. Spoiler: Blur panel On first main camera i attached script BlurBlitter, that shot camera screen and copy it to render texture named MainUIRender. Doesnt matter, is it asset or you creating RenderTexture at runtime. Code: Code (CSharp): using UnityEngine; namespace Leonin { [RequireComponent(typeof(Camera))] public class BlurBlitter : MonoBehaviour { //TODO Replace to config [SerializeField] private float blitDelay; [SerializeField] private RenderTexture targetRenderTexture; [SerializeField] private Material blurMaterial; private RenderTexture temporaryRenderTexture; private Camera mainCamera; private float timer; private bool blitOn; public bool BlitOn { get => blitOn; set { blitOn = value; mainCamera.enabled = !blitOn; timer = blitOn ? blitDelay : 0; } } private void Awake() { mainCamera = GetComponent<Camera>(); targetRenderTexture.width = Screen.width; targetRenderTexture.height = Screen.height; } private void Update() { if (!BlitOn) { return; } timer += Time.deltaTime; } private void LateUpdate() { if (timer > blitDelay) { timer -= blitDelay; mainCamera.Render(); } } private void OnPostRender() { if (!BlitOn) { return; } Graphics.Blit(mainCamera.activeTexture, targetRenderTexture, blurMaterial); mainCamera.targetTexture = null; RenderTexture.ReleaseTemporary(temporaryRenderTexture); } private void OnPreRender() { if (!BlitOn) { return; } temporaryRenderTexture = RenderTexture.GetTemporary(Screen.width, Screen.height); mainCamera.targetTexture = temporaryRenderTexture; } private void OnValidate() { if (blitDelay < 0) { blitDelay = 0; } } } } Render() calls render callbacks where happens all magic. In another client script you should change BlitOn. Also i added blit delay for FPS to configure how often image blur processing will occur. In my example, i use DoTween for smooth change focus and control second camera: Code (CSharp): using DG.Tweening; using DG.Tweening.Core; using System.Threading.Tasks; using UnityEngine; namespace Leonin { public class BlurService : MonoBehaviour { [SerializeField] private BlurBlitter blitter; [SerializeField] private Material blurMaterial; [SerializeField] private Camera afterBlurCamera; private const string SHADER_BLUR_VALUE_NAME = "_Focus"; private const float MIN_BLUR = 0.95F; private const float MAX_BLUR = 1F; private void Awake() { blurMaterial.SetFloat(SHADER_BLUR_VALUE_NAME, MAX_BLUR); } public async Task SetBlur(float value, float duration) { if (value > 0) { blitter.BlitOn = true; afterBlurCamera.enabled = true; } if (duration == 0) { blurMaterial.SetFloat(SHADER_BLUR_VALUE_NAME, Normalize(value)); return; } DOGetter<float> blurGetter = () => blurMaterial.GetFloat(SHADER_BLUR_VALUE_NAME); DOSetter<float> blurSetter = (v) => blurMaterial.SetFloat(SHADER_BLUR_VALUE_NAME, v); await DOTween.To(blurGetter, blurSetter, Normalize(value), duration).AsyncWaitForCompletion(); if (value == 0) { blitter.BlitOn = false; afterBlurCamera.enabled = false; } } private static float Normalize(float value) { float invert = 1 - value; float normalized = MIN_BLUR + invert * (MAX_BLUR - MIN_BLUR); return normalized; } } }