Unity高斯模糊详解

兼顾效果和性能的高斯模糊实现

老规矩完整代码放在最后

1.片元着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fixed4 frag (v2f i) : SV_Target
{
half4 col = 0;
[unroll]
for (int j = samplingCount - 1; j > 0; j--)
{
col += tex2D(_MainTex, i.uv - (_Offsets.xy * j)) * _Weights[j];
}
[unroll]
for (int j = 0; j < samplingCount; j++)
{
col += tex2D(_MainTex, i.uv + (_Offsets.xy * j)) * _Weights[j];
}
return col;
}
  • 根据输入的偏移量(offset)做采样,并乘上对应的权重(weights),累加作为当前像素的最终颜色
  • 两个循环是在正负两个方向做采样

2.高斯函数计算权重

高斯函数:$f(x) = \frac{1}{\sqrt{2\pi\sigma^2}} \cdot e^{-\frac{x^2}{2\sigma^2}}$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private float[] _weights = new float[10];

private void UpdateWeights()
{
float total = 0;
float d = _blur * _blur * 0.001f;
for (int i = 0; i < _weights.Length; i++)
{
float x = i * 2f;
float w = Mathf.Exp(-0.5f * (x * x) / d);
_weights[i] = w;
if (i > 0)
{
w *= 2.0f;
}
total += w;
}
for (int i = 0; i < _weights.Length; i++)
{
_weights[i] /= total;
}
}
  • _blur参数作为标准差,用于调整模糊强度
  • _weights数组的长度为采样点数目,分别计算每个采样点的权重,这里对高斯函数进行了简化
  • total累计权重总和,用于最后做归一化

3.渲染

1
2
3
4
5
6
7
8
9
10
11
12
public void Blur()
{
Graphics.Blit(_texture, _rt1);
_material.SetFloatArray("_Weights", _weights);
float x = _offset / _rt1.width;
float y = _offset / _rt1.height;
_material.SetVector("_Offsets", new Vector4(x, 0, 0, 0));
Graphics.Blit(_rt1, _rt2, _material);
_material.SetVector("_Offsets", new Vector4(0, y, 0, 0));
Graphics.Blit(_rt2, _rt1, _material);
_renderer.material.mainTexture = _rt1;
}
  • 分别计算水平方向和竖直方向的偏移量并将参数传入材质进行渲染
  • 两次一维卷积要比二维卷积性能更好,降低计算量,所以这里在两个分别做渲染
这样出来的模糊效果已经很不错,如果想要更好的模糊效果可以在采样和权重计算上再优化
完结撒花~

C#

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
public class GaussianBlur : MonoBehaviour
{
[SerializeField]
private Texture _texture;
[SerializeField]
private Shader _shader;
[SerializeField, Range(1f, 10f)]
private float _offset = 1f;
[SerializeField, Range(10f, 1000f)]
private float _blur = 100f;

private Material _material;
private Renderer _renderer;
private RenderTexture _rt1;
private RenderTexture _rt2;
private float[] _weights = new float[10];
private bool _isInitialized = false;

#region ### MonoBehaviour ###
private void Awake()
{
Initialize();
}
private void OnValidate()
{
if (!Application.isPlaying)
{
return;
}
UpdateWeights();
Blur();
}
#endregion ### MonoBehaviour ###

private void Initialize()
{
if (_isInitialized)
{
return;
}
_material = new Material(_shader);
_material.hideFlags = HideFlags.HideAndDontSave;

_rt1 = new RenderTexture(_texture.width / 2, _texture.height / 2, 0, RenderTextureFormat.ARGB32);
_rt2 = new RenderTexture(_texture.width / 2, _texture.height / 2, 0, RenderTextureFormat.ARGB32);

_renderer = GetComponent<Renderer>();
UpdateWeights();
_isInitialized = true;
}

public void Blur()
{
if (!_isInitialized)
{
Initialize();
}

Graphics.Blit(_texture, _rt1);
_material.SetFloatArray("_Weights", _weights);
float x = _offset / _rt1.width;
float y = _offset / _rt1.height;

_material.SetVector("_Offsets", new Vector4(x, 0, 0, 0));
Graphics.Blit(_rt1, _rt2, _material);

_material.SetVector("_Offsets", new Vector4(0, y, 0, 0));
Graphics.Blit(_rt2, _rt1, _material);

_renderer.material.mainTexture = _rt1;
}

private void UpdateWeights()
{
float total = 0;
float d = _blur * _blur * 0.001f;
for (int i = 0; i < _weights.Length; i++)
{
float x = i * 2f;
float w = Mathf.Exp(-0.5f * (x * x) / d);
_weights[i] = w;
if (i > 0)
{
w *= 2.0f;
}
total += w;
}
for (int i = 0; i < _weights.Length; i++)
{
_weights[i] /= total;
}
}
}

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
Shader "Custom/BlurEffect"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
ZTest Off
ZWrite Off
Cull Back

CGPROGRAM
#pragma fragmentoption ARB_precision_hint_fastest
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float4 vertex : SV_POSITION;
half2 uv : TEXCOORD0;
};

sampler2D _MainTex;

half4 _Offsets;

static const int samplingCount = 10;
half _Weights[samplingCount];

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;

return o;
}

fixed4 frag (v2f i) : SV_Target
{
half4 col = 0;

[unroll]
for (int j = samplingCount - 1; j > 0; j--)
{
col += tex2D(_MainTex, i.uv - (_Offsets.xy * j)) * _Weights[j];
}

[unroll]
for (int j = 0; j < samplingCount; j++)
{
col += tex2D(_MainTex, i.uv + (_Offsets.xy * j)) * _Weights[j];
}

return col;
}
ENDCG
}
}
}


Unity高斯模糊详解
https://baifabaiquan.cn/2023/12/11/unity高斯模糊详解/
作者
白发败犬
发布于
2023年12月11日
许可协议