Unity shader学习之屏幕后期处理效果之高斯模糊
高斯模糊,见 百度百科。
也使用卷积来实现,每个卷积元素的公式为:
其中б是标准方差,一般取值为1。
x和y分别对应当前位置到卷积中心的整数距离。
由于需要对高斯核中的权重进行归一化,即使所有权重相加为1,因此e前面的系数实际不会对结果产生任何影响。
转载请注明出处:http://www.cnblogs.com/jietian331/p/7238032.html
综上,公式简化为:
G(x,y) = e-(x*x+y*y)/2
因此,高斯核计算代码如下:
1 using System; 2 3 namespace TestShell 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine("输入需要得到的高斯卷积核的维数(如3,5,7...):"); 10 11 string input = Console.ReadLine(); 12 int size; 13 14 if (!int.TryParse(input, out size)) 15 { 16 Console.WriteLine("不是数字..."); 17 return; 18 } 19 20 // 计算 21 double[] r2 = null; 22 double[,] r = null; 23 try 24 { 25 r = CalcGaussianBlur(size, out r2); 26 } 27 catch (Exception ex) 28 { 29 Console.WriteLine("错误: " + ex.Message); 30 } 31 32 if (r != null && r2 != null) 33 { 34 // 卷积如下: 35 Console.WriteLine(); 36 Console.WriteLine("{0}x{0}的高斯卷积核如下:", size); 37 for (int i = 0; i < r.GetLongLength(0); i++) 38 { 39 for (int j = 0; j < r.GetLongLength(1); j++) 40 { 41 Console.Write("{0:f4}\t", r[i, j]); 42 } 43 Console.WriteLine(); 44 } 45 Console.WriteLine(); 46 47 Console.WriteLine("可拆成2个一维的数组:"); 48 for (int i = 0; i < r2.Length; i++) 49 { 50 Console.Write("{0:f4}\t", r2[i]); 51 } 52 Console.WriteLine(); 53 Console.WriteLine(); 54 55 Console.WriteLine("验证,使用这2个一维的数组也可以得到同样的结果:"); 56 for (int i = 0; i < size; i++) 57 { 58 for (int j = 0; j < size; j++) 59 { 60 Console.Write("{0:f4}\t", r2[i] * r2[j]); 61 62 } 63 Console.WriteLine(); 64 } 65 } 66 67 Console.WriteLine(); 68 Console.WriteLine("按任意键结束..."); 69 Console.ReadKey(); 70 } 71 72 static double[,] CalcGaussianBlur(int size, out double[] r2) 73 { 74 if (size < 3) 75 throw new ArgumentException("size < 3"); 76 if (size % 2 != 1) 77 throw new ArgumentException("size % 2 != 1"); 78 79 double[,] r = new double[size, size]; 80 r2 = new double[size]; 81 int center = (int)Math.Floor(size / 2f); 82 double sum = 0; 83 84 for (int i = 0; i < size; i++) 85 { 86 for (int j = 0; j < size; j++) 87 { 88 int x = Math.Abs(i - center); 89 int y = Math.Abs(j - center); 90 double d = CalcItem(x, y); 91 r[i, j] = d; 92 sum += d; 93 } 94 } 95 96 for (int i = 0; i < size; i++) 97 { 98 for (int j = 0; j < size; j++) 99 { 100 r[i, j] /= sum; 101 if (i == j) 102 r2[i] = Math.Sqrt(r[i, i]); 103 } 104 } 105 106 return r; 107 } 108 109 static double CalcItem(int x, int y) 110 { 111 return Math.Pow(Math.E, -(x * x + y * y) / 2d); 112 } 113 } 114 }
工具在: https://files.cnblogs.com/files/jietian331/CalcGaussianBlur.zip
一个5 x 5的高斯核如下:
使用2个一维数组可简化计算量,提高性能,通过观察可知,只需要计3个数:
使用unity shader屏幕后期处理来实现高斯模糊,代码如下。
子类:
1 using UnityEngine; 2 3 public class GaussianBlurRenderer : PostEffectRenderer 4 { 5 [Range(1, 8)] 6 [SerializeField] 7 public int m_downSample = 2; // 降采样率 8 [Range(0, 4)] 9 [SerializeField] 10 public int m_iterations = 3; // 迭代次数 11 [Range(0.2f, 3f)] 12 [SerializeField] 13 public float m_blurSpread = 0.6f; // 模糊扩散量 14 15 protected override void OnRenderImage(RenderTexture src, RenderTexture dest) 16 { 17 int w = (int)(src.width / m_downSample); 18 int h = (int)(src.height / m_downSample); 19 RenderTexture buffer0 = RenderTexture.GetTemporary(w, h); 20 RenderTexture buffer1 = RenderTexture.GetTemporary(w, h); 21 buffer0.filterMode = FilterMode.Bilinear; 22 buffer1.filterMode = FilterMode.Bilinear; 23 Graphics.Blit(src, buffer0); 24 25 for (int i = 0; i < m_iterations; i++) 26 { 27 Mat.SetFloat("_BlurSpread", 1 + i * m_blurSpread); 28 29 Graphics.Blit(buffer0, buffer1, Mat, 0); 30 Graphics.Blit(buffer1, buffer0, Mat, 1); 31 } 32 33 Graphics.Blit(buffer0, dest); 34 RenderTexture.ReleaseTemporary(buffer0); 35 RenderTexture.ReleaseTemporary(buffer1); 36 } 37 38 protected override string ShaderName 39 { 40 get { return "Custom/Gaussian Blur"; } 41 } 42 }
shader:
1 // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 3 Shader "Custom/Gaussian Blur" 4 { 5 Properties 6 { 7 _MainTex("Main Texture", 2D) = "white" {} 8 _BlurSpread("Blur Spread", float) = 1 9 } 10 11 SubShader 12 { 13 CGINCLUDE 14 15 sampler2D _MainTex; 16 float4 _MainTex_TexelSize; 17 uniform float _BlurSpread; 18 19 struct appdata 20 { 21 float4 vertex : POSITION; 22 float2 uv : TEXCOORD0; 23 }; 24 25 struct v2f 26 { 27 float4 pos : SV_POSITION; 28 float2 uv[5] : TEXCOORD0; 29 }; 30 31 v2f vertHorizontal(appdata v) 32 { 33 v2f o; 34 o.pos = UnityObjectToClipPos(v.vertex); 35 float tsx = _MainTex_TexelSize.x * _BlurSpread; 36 o.uv[0] = v.uv + float2(tsx * -2, 0); 37 o.uv[1] = v.uv + float2(tsx * -1, 0); 38 o.uv[2] = v.uv; 39 o.uv[3] = v.uv + float2(tsx * 1, 0); 40 o.uv[4] = v.uv + float2(tsx * 2, 0); 41 return o; 42 } 43 44 v2f vertVertical(appdata v) 45 { 46 v2f o; 47 o.pos = UnityObjectToClipPos(v.vertex); 48 float tsy = _MainTex_TexelSize.y * _BlurSpread; 49 o.uv[0] = v.uv + float2(0, tsy * -2); 50 o.uv[1] = v.uv + float2(0, tsy * -1); 51 o.uv[2] = v.uv; 52 o.uv[3] = v.uv + float2(0, tsy * 1); 53 o.uv[4] = v.uv + float2(0, tsy * 2); 54 return o; 55 } 56 57 fixed4 frag(v2f i) : SV_TARGET 58 { 59 float g[3] = {0.0545, 0.2442, 0.4026}; 60 fixed4 col = tex2D(_MainTex, i.uv[2]) * g[2]; 61 for(int k = 0; k < 2; k++) 62 { 63 col += tex2D(_MainTex, i.uv[k]) * g[k]; 64 col += tex2D(_MainTex, i.uv[4 - k]) * g[k]; 65 } 66 return col; 67 } 68 69 ENDCG 70 71 Pass 72 { 73 Name "HORIZONTAL" 74 ZTest Always 75 ZWrite Off 76 Cull Off 77 78 CGPROGRAM 79 #pragma vertex vertHorizontal 80 #pragma fragment frag 81 ENDCG 82 } 83 84 Pass 85 { 86 Name "VERTICAL" 87 ZTest Always 88 ZWrite Off 89 Cull Off 90 91 CGPROGRAM 92 #pragma vertex vertVertical 93 #pragma fragment frag 94 ENDCG 95 } 96 } 97 98 Fallback Off 99 }
调整参数:
DownSample,即降采样率,越大性能越好,图像越模糊,但过大可能会使图像像素化。
Iteraitions, 即迭代次数,越大图像模糊效果越好,但性能也会下降。
BlurSpread,即模糊扩散量,越大图像越模糊,但过大会造成虚影。
效果如下:
URP管线下如何实现高斯模糊,shader代码如下:
1 Shader "TA/Unlit/Feature/Post Process Blur" 2 { 3 Properties 4 { 5 _MainTex("Main Texture", 2D) = "white" {} 6 _BlurSpread("Blur Spread", float) = 1 7 } 8 9 SubShader 10 { 11 Tags 12 { 13 "RenderType" = "Opaque" 14 "RenderPipeline" = "UniversalPipeline" 15 } 16 17 HLSLINCLUDE 18 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" 19 20 TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); 21 float4 _MainTex_TexelSize; 22 float _BlurSpread; 23 24 struct appdata 25 { 26 float4 vertex : POSITION; 27 float2 uv : TEXCOORD0; 28 }; 29 30 struct v2f 31 { 32 float4 pos : SV_POSITION; 33 float2 uv[5] : TEXCOORD0; 34 }; 35 36 v2f vertHorizontal(appdata v) 37 { 38 v2f o; 39 o.pos = TransformObjectToHClip(v.vertex.xyz); 40 float tsx = _MainTex_TexelSize.x * _BlurSpread; 41 o.uv[0] = v.uv + float2(tsx * -2, 0); 42 o.uv[1] = v.uv + float2(tsx * -1, 0); 43 o.uv[2] = v.uv; 44 o.uv[3] = v.uv + float2(tsx * 1, 0); 45 o.uv[4] = v.uv + float2(tsx * 2, 0); 46 return o; 47 } 48 49 v2f vertVertical(appdata v) 50 { 51 v2f o; 52 o.pos = TransformObjectToHClip(v.vertex.xyz); 53 float tsy = _MainTex_TexelSize.y * _BlurSpread; 54 o.uv[0] = v.uv + float2(0, tsy * -2); 55 o.uv[1] = v.uv + float2(0, tsy * -1); 56 o.uv[2] = v.uv; 57 o.uv[3] = v.uv + float2(0, tsy * 1); 58 o.uv[4] = v.uv + float2(0, tsy * 2); 59 return o; 60 } 61 62 half4 frag(v2f i) : SV_TARGET 63 { 64 float g[3] = {0.0545, 0.2442, 0.4026}; 65 half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[2]) * g[2]; 66 for(int k = 0; k < 2; k++) 67 { 68 col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[k]) * g[k]; 69 col += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv[4 - k]) * g[k]; 70 } 71 return col; 72 } 73 ENDHLSL 74 75 Pass 76 { 77 Name "HORIZONTAL" 78 ZTest Always 79 ZWrite Off 80 Cull Off 81 82 HLSLPROGRAM 83 #pragma vertex vertHorizontal 84 #pragma fragment frag 85 ENDHLSL 86 } 87 88 Pass 89 { 90 Name "VERTICAL" 91 ZTest Always 92 ZWrite Off 93 Cull Off 94 95 HLSLPROGRAM 96 #pragma vertex vertVertical 97 #pragma fragment frag 98 ENDHLSL 99 } 100 } 101 102 FallBack "Hidden/Universal Render Pipeline/FallbackError" 103 }
Feature代码如下:
1 using Frame; 2 using System; 3 using UnityEngine; 4 using UnityEngine.Rendering; 5 using UnityEngine.Rendering.Universal; 6 7 namespace TA 8 { 9 public class URPFeature_Blur : ScriptableRendererFeature 10 { 11 #region Pass & Setting 12 13 class Settings 14 { 15 public int m_DownSample = 2; //降采样率 16 public int m_Iterations = 3; //迭代次数 17 public float m_BlurSpread = 0.6f; //模糊扩散量 18 19 NormalAssetItem<Material> m_Asset; 20 21 public Material Mat 22 { 23 get 24 { 25 if (m_Asset == null && YooAssetPatchMgr.Singleton.IsPatchDone) 26 { 27 m_Asset = new NormalAssetItem<Material>("feature@featureblur", YooAssetPackages.PackageArtCloth); 28 m_Asset.Load(); 29 } 30 31 return m_Asset.IsLoadComplete ? m_Asset.AssetObj : null; 32 } 33 } 34 35 public void Release() 36 { 37 if (m_Asset != null) 38 { 39 m_Asset.Destroy(); 40 m_Asset = null; 41 } 42 } 43 } 44 45 class URPPass_Blur : ScriptableRenderPass 46 { 47 Settings m_Settings; 48 RenderTargetIdentifier m_Source; 49 int m_TempRTID0 = Shader.PropertyToID($"{nameof(URPFeature_Blur)}_TempRT0"); 50 int m_TempRTID1 = Shader.PropertyToID($"{nameof(URPFeature_Blur)}_TempRT1"); 51 52 public URPPass_Blur(Settings settings) 53 { 54 m_Settings = settings; 55 } 56 57 public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) 58 { 59 m_Source = renderingData.cameraData.renderer.cameraColorTarget; 60 } 61 62 public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) 63 { 64 CommandBuffer cmd = CommandBufferPool.Get($"{nameof(URPFeature_Blur)}_cmd"); 65 66 var data = renderingData.cameraData.cameraTargetDescriptor; 67 int width = data.width / m_Settings.m_DownSample; 68 int height = data.height / m_Settings.m_DownSample; 69 70 cmd.GetTemporaryRT(m_TempRTID0, width, height, 0, FilterMode.Trilinear, RenderTextureFormat.ARGB32); 71 cmd.Blit(m_Source, m_TempRTID0); 72 73 for (int i = 0; i < m_Settings.m_Iterations; ++i) 74 { 75 m_Settings.Mat.SetFloat("_BlurSpread", 1 + i * m_Settings.m_BlurSpread); 76 77 // 第一轮 78 cmd.GetTemporaryRT(m_TempRTID1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); 79 cmd.Blit(m_TempRTID0, m_TempRTID1, m_Settings.Mat, 0); 80 cmd.ReleaseTemporaryRT(m_TempRTID0); 81 82 // 第二轮 83 cmd.GetTemporaryRT(m_TempRTID0, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32); 84 cmd.Blit(m_TempRTID1, m_TempRTID0, m_Settings.Mat, 1); 85 cmd.ReleaseTemporaryRT(m_TempRTID1); 86 } 87 88 cmd.Blit(m_TempRTID0, m_Source); 89 cmd.ReleaseTemporaryRT(m_TempRTID0); 90 91 context.ExecuteCommandBuffer(cmd); 92 CommandBufferPool.Release(cmd); 93 } 94 } 95 96 #endregion 97 98 99 Settings m_Settings; 100 URPPass_Blur m_Pass; 101 102 103 public override void Create() 104 { 105 m_Settings = new(); 106 m_Pass = new URPPass_Blur(m_Settings); 107 m_Pass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; 108 } 109 110 public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) 111 { 112 if (m_Settings.Mat) 113 { 114 renderer.EnqueuePass(m_Pass); 115 } 116 } 117 118 protected override void Dispose(bool disposing) 119 { 120 base.Dispose(disposing); 121 if (m_Settings != null) 122 { 123 m_Settings.Release(); 124 m_Settings = null; 125 } 126 } 127 128 /// <summary> 129 /// 设置参数 130 /// </summary> 131 /// <param name="downSample">降采样率</param> 132 /// <param name="iterations">迭代次数</param> 133 /// <param name="blurSpread">模糊扩散量</param> 134 public void SetParam(int downSample, int iterations, float blurSpread) 135 { 136 m_Settings.m_DownSample = downSample; 137 m_Settings.m_Iterations = iterations; 138 m_Settings.m_BlurSpread = blurSpread; 139 } 140 141 142 143 } 144 }