• 【Unity Shader】屏幕后处理3.0:均值模糊和高斯模糊


    发现之前学习记录的太过详细,导致整理的过程占用太长的时间了,这篇之后博客重要的是掌握实现过程,关于基础的理论会更多的放上别人写得更好的文章。


    参考:【Unity Shader编程】之十五 屏幕高斯模糊(Gaussian Blur)后期特效的实现

    1 基础概念

    1.1 模糊

    高斯模糊只是各种模糊方式中的一种。模糊,从图像处理角度可以理解成每个像素都取周围像素的均值,数值上会呈现一种“平滑”的效果,图形上就是中心像素失去细节,达到模糊效果!显然,既然是取均值,那自然这个均值的范围越大,模糊效果越厉害。

    那么问题就来了——卷积核内每个网格内的权重该怎么分配? 

    1.2 均值模糊

    是直接均匀分配吗?如果周围像素取均值权重都一样,也就是取算数平均值模糊,这种模糊方式叫做均值模糊,均值模糊的卷积核很简单:(下图来自忘了哪一篇的参考文章)

    1.3 什么是降采样

    哦对了!在实践均值/高斯模糊之前,还需要知道一个概念——降采样(DownSample)。字面理解其实就是降低采样率,一般会给个降采样系数。例如降采样系数取2的话,就是在源图像上每行每列每隔2个像素组成一幅图像。降采样系数越大,我们需要处理的像素就少了,那么图片就会越模糊,但如果太大了,得到的结果可能直接像素化了。

    1.4 什么是高斯模糊

    什么是高斯模糊?——使用正态分布作为卷积权重表的像素平滑方法。

    正态分布

    这是正态分布公式:

    这是正态分布图像:

     正态分布我们再熟悉不过了, 数学中常常被我们拿来处理各种概率计算问题,而且正态分布计算非常简单,可以快速得出结果,非要详细学习的话,推荐看看:正态分布在日常生活中的应用(一) - 知乎 (zhihu.com),这里不再赘述这些基础知识。

    高斯函数

    上面给出的正态分布,都是一维的关系,而我们想基于图像做正态分布,那就需要二维的正态分布。正态分布密度函数叫做高斯函数(Gaussian function),高斯函数的二维形式可以根据一维形式推导出来,结果如下:

    有了这个函数我们能干嘛?还记得吗,第1节中我们探讨了“权重”的问题,而这个函数刚好能帮我们解决权重问题,因此接下来我们需要计算权重矩阵&模糊值,这一部分直接参考真正搞懂均值模糊、中值模糊、高斯模糊、双边模糊中的内容就行。而经过一系列操作后获得的权重集合,就可以当作进行高斯模糊卷积操作的卷积核。

    拆分高斯卷积核

    本身高斯函数就是正态分布的密度函数,而二维的正态分布函数本身就可以拆分成x、y两个轴向上的一维正态分布函数, 所以将卷积核拆分成两个一维的卷积核是可行的。 详细解释可参考:高斯模糊用到的二维高斯卷积核,为什么可以拆成两个一维的高斯卷积核?

    行,理论知识点到为止,这里就先简单实践一下均值模糊和高斯模糊的效果:

    2 实现均值模糊

    实现模糊效果,一般会给用户提供的可以调整的参数有

    • 迭代次数
    • 模糊范围
    • 降采样系数

    实现均值模糊很简单,我取了3X3的卷积核,C#代码和Shader代码如下:

    2.1 C#脚本 

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Rendering;
    5. [ExecuteInEditMode]
    6. public class BoxBlur : MonoBehaviour
    7. {
    8. public Shader blurShader;
    9. public Material blurMaterial;
    10. [Header("迭代次数")]
    11. [Range(0, 4)]
    12. public int blurIterations = 1;
    13. [Header("模糊范围")]
    14. [Range(0.2f, 3.0f)]
    15. public float blurSpread = 0.2f;
    16. [Header("降采样系数")]
    17. [Range(1, 8)]
    18. public int downSample = 2;
    19. private void OnRenderImage(RenderTexture source, RenderTexture destination)
    20. {
    21. if(blurMaterial != null)
    22. {
    23. int rtW = source.width / downSample;
    24. int rtH = source.height / downSample;
    25. //定义缓存rt
    26. RenderTexture rt = RenderTexture.GetTemporary(rtW, rtH, 0);
    27. //source缩放后,存到了rt上
    28. Graphics.Blit(source, rt, blurMaterial);
    29. //开始迭代模糊
    30. for (int i = 0; i < blurIterations; i++)
    31. {
    32. blurMaterial.SetFloat("_BlurSize", blurSpread);
    33. Graphics.Blit(rt, source, blurMaterial);
    34. Graphics.Blit(source, rt, blurMaterial);
    35. }
    36. //输出结果
    37. Graphics.Blit (rt, destination, blurMaterial);
    38. //释放缓存
    39. RenderTexture.ReleaseTemporary(rt);
    40. }
    41. else
    42. {
    43. Debug.Log("Please input your Material");
    44. Graphics.Blit(source, destination);
    45. }
    46. }
    47. }

    2.2 Shader代码

    1. Shader "Unity Shaders Book/Chapter 12/BoxBlur"
    2. {
    3. Properties
    4. {
    5. _MainTex ("Texture", 2D) = "white" {}
    6. }
    7. SubShader
    8. {
    9. ZTest Always
    10. Cull Off
    11. ZWrite Off
    12. Pass {
    13. CGPROGRAM
    14. #pragma vertex vert
    15. #pragma fragment BoxBlur
    16. #include "UnityCG.cginc"
    17. //properties
    18. sampler2D _MainTex;
    19. half4 _MainTex_TexelSize;
    20. float _BlurSize;
    21. struct v2f {
    22. float4 pos : SV_POSITION;
    23. half2 uv[9] : TEXCOORD0;
    24. };
    25. v2f vert(appdata_img v) {
    26. v2f o;
    27. o.pos = UnityObjectToClipPos(v.vertex);
    28. half2 uv = v.texcoord;
    29. //加入_BlurSize控制采样距离
    30. o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1) * _BlurSize;
    31. o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1) * _BlurSize;
    32. o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1) * _BlurSize;
    33. o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0) * _BlurSize;
    34. o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0) * _BlurSize;
    35. o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0) * _BlurSize;
    36. o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1) * _BlurSize;
    37. o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1) * _BlurSize;
    38. o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1) * _BlurSize;
    39. return o;
    40. }
    41. fixed3 BoxBlur(v2f i) : SV_Target {
    42. fixed4 color = fixed4(0, 0, 0, 0);
    43. for(int j=0;j<9;j++){
    44. color +=tex2D(_MainTex, i.uv[j]);
    45. }
    46. return color/9.0;
    47. }
    48. ENDCG
    49. }
    50. }
    51. }

    2.3 呈现效果

    3 实现高斯模糊

    没啥好说的,就是用了两个Pass分别做了水平和竖直向的高斯模糊处理。 

    3.1 C#脚本

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Rendering;
    5. [ExecuteInEditMode]
    6. public class GaussianBlur : MonoBehaviour
    7. {
    8. public Shader blurShader;
    9. public Material blurMaterial;
    10. [Header("迭代次数")]
    11. [Range(0, 4)]
    12. public int blurIterations = 1;
    13. [Header("模糊范围")]
    14. [Range(0.2f, 3.0f)]
    15. public float blurSpread = 0.2f;
    16. [Header("降采样系数")]
    17. [Range(1, 8)]
    18. public int downSample = 2;
    19. private void OnRenderImage(RenderTexture source, RenderTexture destination)
    20. {
    21. if(blurMaterial != null)
    22. {
    23. int rtW = source.width / downSample;
    24. int rtH = source.height / downSample;
    25. //定义缓存rt
    26. RenderTexture rt0 = RenderTexture.GetTemporary(rtW, rtH, 0);
    27. rt0.filterMode = FilterMode.Bilinear; //设置滤波模式
    28. //source缩放后,存到了rt0
    29. Graphics.Blit(source, rt0, blurMaterial);
    30. //开始迭代模糊
    31. for (int i = 0; i < blurIterations; i++)
    32. {
    33. blurMaterial.SetFloat("_BlurSize", blurSpread);
    34. RenderTexture rt1 = RenderTexture.GetTemporary(rtW, rtH, 0);
    35. //第一个Pass
    36. Graphics.Blit(rt0, rt1, blurMaterial, 0);
    37. RenderTexture.ReleaseTemporary(rt0); //把rt0释放
    38. rt0 = rt1; //把第一个Pass的结果给rt0
    39. rt1 = RenderTexture.GetTemporary(rtW, rtH, 0); //创建个新的rt1
    40. //第二个Pass
    41. Graphics.Blit(rt0, rt1, blurMaterial, 1);
    42. RenderTexture.ReleaseTemporary(rt0);
    43. rt0 = rt1; //继续进行下一个迭代的(如果有的话)
    44. }
    45. //输出结果
    46. Graphics.Blit (rt0, destination, blurMaterial);
    47. //释放缓存
    48. RenderTexture.ReleaseTemporary(rt0);
    49. }
    50. else
    51. {
    52. Debug.Log("Please input your Material");
    53. Graphics.Blit(source, destination);
    54. }
    55. }
    56. }

    3.2 Shader代码

    1. Shader "Unity Shaders Book/Chapter 12/GaussianBlur"
    2. {
    3. Properties
    4. {
    5. _MainTex ("Texture", 2D) = "white" {}
    6. }
    7. SubShader
    8. {
    9. //CGINCLUDE--ENDCG中定义的可以定义一系列Pass中会用到的函数,相当于一个头文件的作用:
    10. CGINCLUDE
    11. #include "UnityCG.cginc"
    12. sampler2D _MainTex;
    13. half4 _MainTex_TexelSize;
    14. float _BlurSize;
    15. struct v2f {
    16. float4 pos : SV_POSITION;
    17. half2 uv[5] : TEXCOORD0;
    18. };
    19. //水平
    20. v2f vertBlurVertical(appdata_img v) {
    21. v2f o;
    22. o.pos = UnityObjectToClipPos(v.vertex);
    23. half2 uv = v.texcoord;
    24. o.uv[0] = uv;
    25. o.uv[1] = uv + float2(0.0f, _MainTex_TexelSize.y * 1.0 * _BlurSize);
    26. o.uv[2] = uv - float2(0.0f, _MainTex_TexelSize.y * 1.0 * _BlurSize);
    27. o.uv[3] = uv + float2(0.0f, _MainTex_TexelSize.y * 2.0 * _BlurSize);
    28. o.uv[4] = uv - float2(0.0f, _MainTex_TexelSize.y * 2.0 * _BlurSize);
    29. return o;
    30. }
    31. //竖直
    32. v2f vertBlurHorizontal(appdata_img v) {
    33. v2f o;
    34. o.pos = UnityObjectToClipPos(v.vertex);
    35. half2 uv = v.texcoord;
    36. o.uv[0] = uv;
    37. o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0 * _BlurSize, 0.0f);
    38. o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0 * _BlurSize, 0.0f);
    39. o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0 * _BlurSize, 0.0f);
    40. o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0 * _BlurSize, 0.0f);
    41. return o;
    42. }
    43. fixed4 GaussianBlur(v2f i) : SV_Target {
    44. float weight[3] = {0.4026, 0.2442, 0.0545};
    45. fixed3 color = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
    46. for(int j=1;j<3;j++) {
    47. color += tex2D(_MainTex, i.uv[2*j-1]).rgb * weight[j];
    48. color += tex2D(_MainTex, i.uv[2*j]).rgb * weight[j];
    49. }
    50. return fixed4(color, 1.0);
    51. }
    52. ENDCG
    53. ZTest Always
    54. Cull Off
    55. ZWrite Off
    56. //水平的Pass
    57. Pass {
    58. NAME "GAUSSIANBLUR_BLUR_VERTICAL"
    59. CGPROGRAM
    60. #pragma vertex vertBlurVertical
    61. #pragma fragment GaussianBlur
    62. ENDCG
    63. }
    64. //竖直
    65. Pass {
    66. NAME "GAUSSIANBLUR_BLUR_HORIZONTAL"
    67. CGPROGRAM
    68. #pragma vertex vertBlurHorizontal
    69. #pragma fragment GaussianBlur
    70. ENDCG
    71. }
    72. }
    73. FallBack Off
    74. }

    3.3 呈现效果


    感觉均值模糊和高斯模糊看上去区别不是很明显,可能是我对比的问题~当然,细说的话高斯模糊变化的肯定更平滑,因为均值是直接暴力求均值的,忽略了连续性。

  • 相关阅读:
    Taro小程序富文本解析4种方法
    windows安装MongoDB后进入命令交互界面失败解决方案
    百川的大模型KnowHow
    Qt国际化翻译解决方案
    【管理运筹学】第 7 章 | 图与网络分析(5,最小费用流问题及最小费用最大流问题)
    TDengine小知识-数据文件命名规则
    TensorFlow实现基于卷积神经网络的频域重建MRI图像方法
    时钟有关概念汇总
    python采集火热弹幕数据并做词云图可视化分析
    【机器学习】随机森林及调参 学习笔记
  • 原文地址:https://blog.csdn.net/qq_41835314/article/details/127625084