最近想在UI中加入类似Win7的毛玻璃效果,将弹窗UI的背景虚化。实现的原理很简单,就是对UI的背景做一下Blur。在实现过程中,我尝试了两种方法:
1、直接Blur UI后面的图像。
这是网上很多文章提供的方法,也比较容易想到。可以直接用Shader控制UI 背景Panel上Image组件的渲染。
因为Unity 提供了GrabPass,可以在Shader中很方便的拿到Panel下面的图像,即_GrabTexture。
问题在于如何对_GrabTexture做Blur处理。如果使用普通的高斯模糊,效果并不理想,原因在于模糊半径太小了,导致模糊后的图像太清晰,玻璃“不够毛”。
我前面一篇分析Bloom效果的日志,提供了一种方法,先对图像做向下采样,然后再做模糊处理,这样做出来的模糊效果比较理想,但是无法通过一个Pass实现。而在Image控件上使用的Shader并不能自由的控制Pass,所以只能硬着头皮用一个Pass做大面积的模糊处理。所以我放弃了高斯模糊,而使用发现贴图扰动的方式做模糊处理。代码看起来很Low:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
Shader "Custom/UIFrostedGlass" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _NormalTex ("Sprite Texture", 2D) = "white" {} _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 } 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 GrabPass{ } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityUI.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; half2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; }; v2f vert(appdata_t IN) { v2f OUT; OUT.worldPosition = IN.vertex; OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.worldPosition); OUT.texcoord = half2(IN.texcoord.x, 1-IN.texcoord.y); return OUT; } sampler2D _MainTex; uniform half4 _MainTex_TexelSize; sampler2D _GrabTexture; uniform half4 _GrabTexture_TexelSize; sampler2D _NormalTex; float _RandomParam; static const half4 curve4[7] = { half4(0.0205,0.0205,0.0205,0), half4(0.0855,0.0855,0.0855,0), half4(0.232,0.232,0.232,0), half4(0.324,0.324,0.324,1), half4(0.232,0.232,0.232,0), half4(0.0855,0.0855,0.0855,0), half4(0.0205,0.0205,0.0205,0) }; fixed4 frag(v2f IN) : SV_Target { half4 nor = tex2D(_NormalTex, IN.texcoord *8); half4 nor2 = tex2D(_NormalTex, IN.texcoord *12); half4 nor3 = tex2D(_NormalTex, IN.texcoord *23); half4 nor4 = tex2D(_NormalTex, IN.texcoord *26); half4 nor5 = tex2D(_NormalTex, IN.texcoord *33); half4 nor6 = tex2D(_NormalTex, IN.texcoord *41); half4 nor7 = tex2D(_NormalTex, IN.texcoord *51); half4 nor8 = tex2D(_NormalTex, IN.texcoord *62); half4 nor9 = tex2D(_NormalTex, IN.texcoord *73); half4 nor10 = tex2D(_NormalTex, IN.texcoord *86); half4 nor11 = tex2D(_NormalTex, IN.texcoord *93); half4 nor12 = tex2D(_NormalTex, IN.texcoord *91); half4 norcolor1 = tex2D(_GrabTexture, IN.texcoord + (20 * _GrabTexture_TexelSize * nor.xy)); half4 norcolor2 = tex2D(_GrabTexture, IN.texcoord - (20 * _GrabTexture_TexelSize * nor.yz)); half4 norcolor3 = tex2D(_GrabTexture, IN.texcoord + (20 * _GrabTexture_TexelSize * nor.zx)); half4 norcolor21 = tex2D(_GrabTexture, IN.texcoord - (20 * _GrabTexture_TexelSize * nor2.xy)); half4 norcolor22 = tex2D(_GrabTexture, IN.texcoord + (20 * _GrabTexture_TexelSize * nor2.yz)); half4 norcolor23 = tex2D(_GrabTexture, IN.texcoord - (20 * _GrabTexture_TexelSize * nor2.zx)); half4 norcolor31 = tex2D(_GrabTexture, IN.texcoord + (20 * _GrabTexture_TexelSize * nor3.xy)); half4 norcolor32 = tex2D(_GrabTexture, IN.texcoord - (20 * _GrabTexture_TexelSize * nor3.yz)); half4 norcolor33 = tex2D(_GrabTexture, IN.texcoord + (20 * _GrabTexture_TexelSize * nor3.zx)); half4 norcolor41 = tex2D(_GrabTexture, IN.texcoord - (20 * _GrabTexture_TexelSize * nor4.xy)); half4 norcolor42 = tex2D(_GrabTexture, IN.texcoord + (30 * _GrabTexture_TexelSize * nor4.yz)); half4 norcolor43 = tex2D(_GrabTexture, IN.texcoord - (30 * _GrabTexture_TexelSize * nor4.zx)); half4 norcolor51 = tex2D(_GrabTexture, IN.texcoord + (30 * _GrabTexture_TexelSize * nor5.xy)); half4 norcolor52 = tex2D(_GrabTexture, IN.texcoord - (30 * _GrabTexture_TexelSize * nor5.yz)); half4 norcolor53 = tex2D(_GrabTexture, IN.texcoord + (30 * _GrabTexture_TexelSize * nor5.zx)); half4 norcolor61 = tex2D(_GrabTexture, IN.texcoord - (30 * _GrabTexture_TexelSize * nor6.xy)); half4 norcolor62 = tex2D(_GrabTexture, IN.texcoord + (40 * _GrabTexture_TexelSize * nor6.yz)); half4 norcolor63 = tex2D(_GrabTexture, IN.texcoord - (40 * _GrabTexture_TexelSize * nor6.zx)); half4 norcolor71 = tex2D(_GrabTexture, IN.texcoord + (40 * _GrabTexture_TexelSize * nor7.xy)); half4 norcolor72 = tex2D(_GrabTexture, IN.texcoord - (40 * _GrabTexture_TexelSize * nor7.yz)); half4 norcolor73 = tex2D(_GrabTexture, IN.texcoord + (40 * _GrabTexture_TexelSize * nor7.zx)); half4 norcolor81 = tex2D(_GrabTexture, IN.texcoord - (50 * _GrabTexture_TexelSize * nor8.xy)); half4 norcolor82 = tex2D(_GrabTexture, IN.texcoord + (50 * _GrabTexture_TexelSize * nor8.yz)); half4 norcolor83 = tex2D(_GrabTexture, IN.texcoord - (50 * _GrabTexture_TexelSize * nor8.zx)); half4 norcolor91 = tex2D(_GrabTexture, IN.texcoord + (50 * _GrabTexture_TexelSize * nor9.xy)); half4 norcolor92 = tex2D(_GrabTexture, IN.texcoord - (60 * _GrabTexture_TexelSize * nor9.yz)); half4 norcolor93 = tex2D(_GrabTexture, IN.texcoord + (60 * _GrabTexture_TexelSize * nor9.zx)); half4 norcolor101 = tex2D(_GrabTexture, IN.texcoord - (60 * _GrabTexture_TexelSize * nor10.xy)); half4 norcolor102 = tex2D(_GrabTexture, IN.texcoord + (70 * _GrabTexture_TexelSize * nor10.yz)); half4 norcolor103 = tex2D(_GrabTexture, IN.texcoord - (70 * _GrabTexture_TexelSize * nor10.zx)); half4 norcolor111 = tex2D(_GrabTexture, IN.texcoord + (80 * _GrabTexture_TexelSize * nor11.xy)); half4 norcolor112 = tex2D(_GrabTexture, IN.texcoord - (80 * _GrabTexture_TexelSize * nor11.yz)); half4 norcolor113 = tex2D(_GrabTexture, IN.texcoord + (90 * _GrabTexture_TexelSize * nor11.zx)); half4 norcolor121 = tex2D(_GrabTexture, IN.texcoord - (90 * _GrabTexture_TexelSize * nor12.xy)); half4 norcolor122 = tex2D(_GrabTexture, IN.texcoord + (100 * _GrabTexture_TexelSize * nor12.yz)); half4 norcolor123 = tex2D(_GrabTexture, IN.texcoord - (110 * _GrabTexture_TexelSize * nor12.zx)); half4 color = (norcolor1 + norcolor2 + norcolor3 + norcolor21 + norcolor22 + norcolor23 + norcolor31 + norcolor32 + norcolor33 + norcolor41 + norcolor42 + norcolor43 + norcolor51 + norcolor52 + norcolor53 + norcolor61 + norcolor62 + norcolor63 + norcolor71 + norcolor72 + norcolor73 + norcolor81 + norcolor82 + norcolor83 + norcolor91 + norcolor92 + norcolor93 + norcolor101 + norcolor102 + norcolor103 + norcolor111 + norcolor112 + norcolor113 + norcolor121 + norcolor122 + norcolor123)/36; return color; } ENDCG } } } |
采用了36次采样,初步得到了一个比较理想的效果:
但这种方法也有问题,就是如果Blur图像中存在UI、文字等棱角分明的部分,Blur效果就很差:
所以最终还是放弃了这个比较简单的方案。
2、多Pass Blur
由于Unity的渲染机制,Pass与DrawCall无关,也就是说,多Pass的后渲染处理,只能放在摄像机上在屏幕空间上做,而不能单独控制单个GameObject的渲染。所以,只能借助摄像机进行。结合上一篇Bloom中的方法,要先把场景渲染结果做向下采样处理,处理成一张小图,然后再做Blur处理。
这里用了一个省力的做法,给摄像机提供一个很小的RenderTexture,自动完成向下采样。RT越小,Blur效果越理想,经过测试,我用了1/32屏幕长宽的RT,得到RT后,创建一个Sprite赋值给Image组件,然后再用Shader在Image渲染的时候做Blur处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
using UnityEngine; using System.Collections; using UnityEngine.UI; using Common.Base; public class FrostedGlassPanel : MonoBehaviour { private int mScreenx = 0; private int mScreeny = 0; void Start () { mScreenx = Screen.width; mScreeny = Screen.height; GetComponent<Image>().material = ResourceLoader.LoadResource<Material>("Materials/UIFrostedGlass.mat"); RefreshBackground(); } void OnEnable(){ RefreshBackground(); } void RefreshBackground(){ if(mScreenx <= 0){ //第一次OnEnable时,没有准备好。 return; } RenderTexture rt = new RenderTexture(mScreenx/32, mScreeny/32, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); Camera.main.targetTexture = rt; Camera.main.Render(); RenderTexture.active = rt; Texture2D screenShort = new Texture2D(mScreenx/32, mScreeny/32, TextureFormat.ARGB32, false); screenShort.ReadPixels(new Rect(0, 0, mScreenx/32, mScreeny/32), 0, 0, false); screenShort.wrapMode = TextureWrapMode.Clamp;//不能用Repeat,否则边缘颜色会相互渗透。 screenShort.Apply(); Camera.main.targetTexture = null; RenderTexture.active = null; Destroy(rt); Sprite sp = Sprite.Create(screenShort, new Rect(0, 0, mScreenx/32, mScreeny/32), Vector2.zero); GetComponent<Image>().sprite = sp; } } |
shader如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
Shader "Custom/UIFrostedGlass" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _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 } 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 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityUI.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; half2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; }; v2f vert(appdata_t IN) { v2f OUT; OUT.worldPosition = IN.vertex; OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.worldPosition); OUT.texcoord = half2(IN.texcoord.x, IN.texcoord.y); return OUT; } sampler2D _MainTex; uniform half4 _MainTex_TexelSize; static const half4 curve4[7] = { half4(0.0205,0.0205,0.0205,0), half4(0.0855,0.0855,0.0855,0), half4(0.232,0.232,0.232,0), half4(0.324,0.324,0.324,1), half4(0.232,0.232,0.232,0), half4(0.0855,0.0855,0.0855,0), half4(0.0205,0.0205,0.0205,0) }; fixed4 frag(v2f IN) : SV_Target { half4 blurColor1 = 0; for(int i = 0; i < 7; i++){ blurColor1 += tex2D(_MainTex, IN.texcoord + 0.005 * half2(i-3, 0)) * curve4[i]; } half4 blurColor2 = 0; for(int i = 0; i < 7; i++){ blurColor2 += tex2D(_MainTex, IN.texcoord + 0.005 * half2(0, i-3)) * curve4[i]; } half4 blurColor = (blurColor1 * 0.5 + blurColor2 * 0.5) * 0.9; blurColor.a = 1.0; return blurColor; } ENDCG } } } |
这里发现了几个蛋疼的问题,
1、在OnEnable时,Screen的长宽获取的值是错误的,所以不能在那个时机取屏幕大小。
2、使用了降采样的图像后,在Image的Shader中,_MainTex_TexelSize 的值变成了(1,1,1,1),所以也用不了了。只能先写死一个比例,后面可以通过参数传到Shader中。
最后的结果还算理想: