说到模糊处理我们一般就会想到多种模糊处理方法,如均值模糊,高斯模糊等等方式。
通常用它们来减少图像噪声以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。
均值模糊和高斯模糊它们各有各的优势,均值模糊处理速度更快,实现也相对简单一些,高斯模糊处理效果更好,性能差点,实现相对复杂点。
我们先来实现一下相对简单的均值模糊效果!
均值模糊:
最终效果图如下:
左边为原图,右边为均值模糊处理后的效果
说到模糊处理就不得不说滤波器,也可以叫做卷积核,模糊处理的过程其实就是对原图卷积的一个过程。
滤波器和模糊相关的可以参考这篇文章
说白了均值模糊就是对周围像素点取平均值!
该文章用到的是一个三阶的卷积核
[
1
1
1
1
1
1
1
1
1
]
\begin{bmatrix}1 & 1 & 1\\\\1 &1& 1\\\\1 &1& 1\end{bmatrix}
⎣⎢⎢⎢⎢⎡111111111⎦⎥⎥⎥⎥⎤
如果处理效果不明显,我们可以通过不断迭代加重模糊效果。
具体实现请看以下代码:
Shader如下:
// ---------------------------【均值模糊】---------------------------
//create by 长生但酒狂
Shader "lcl/screenEffect/BoxBlur"
{
// ---------------------------【属性】---------------------------
Properties
{
_MainTex("MainTex", 2D) = "white" {}
}
// ---------------------------【子着色器】---------------------------
SubShader
{
//后处理效果一般都是这几个状态
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode Off }
// ---------------------------【渲染通道】---------------------------
Pass
{
// ---------------------------【CG代码】---------------------------
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//顶点着色器输出结构体
struct VertexOutput
{
float4 pos : SV_POSITION; //顶点位置
float2 uv : TEXCOORD0; //纹理坐标
float4 uv1 : TEXCOORD1; //存储两个uv坐标
float4 uv2 : TEXCOORD2; //存储两个uv坐标
float4 uv3 : TEXCOORD3; //存储两个uv坐标
float4 uv4 : TEXCOORD4; //存储两个uv坐标
};
// ---------------------------【变量申明】---------------------------
sampler2D _MainTex;
//纹理中的单像素尺寸
float4 _MainTex_TexelSize;
// 模糊半径
float _BlurRadius;
// ---------------------------【顶点着色器】---------------------------
VertexOutput vert(appdata_img v)
{
VertexOutput o;
o.pos = UnityObjectToClipPos(v.vertex);
//uv坐标
o.uv = v.texcoord.xy;
//计算周围的8个uv坐标
o.uv1.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, 0) * _BlurRadius;
o.uv1.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, 0) * _BlurRadius;
o.uv2.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(0, 1) * _BlurRadius;
o.uv2.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(0, -1) * _BlurRadius;
o.uv3.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, 1) * _BlurRadius;
o.uv3.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, 1) * _BlurRadius;
o.uv4.xy = v.texcoord.xy + _MainTex_TexelSize.xy * float2(1, -1) * _BlurRadius;
o.uv4.zw = v.texcoord.xy + _MainTex_TexelSize.xy * float2(-1, -1) * _BlurRadius;
return o;
}
// ---------------------------【片元着色器】---------------------------
fixed4 frag(VertexOutput i) : SV_Target
{
fixed4 color = fixed4(0,0,0,0);
color += tex2D(_MainTex, i.uv.xy);
color += tex2D(_MainTex, i.uv1.xy);
color += tex2D(_MainTex, i.uv1.zw);
color += tex2D(_MainTex, i.uv2.xy);
color += tex2D(_MainTex, i.uv2.zw);
color += tex2D(_MainTex, i.uv3.xy);
color += tex2D(_MainTex, i.uv3.zw);
color += tex2D(_MainTex, i.uv4.xy);
color += tex2D(_MainTex, i.uv4.zw);
// 取平均值
return color / 9;
}
ENDCG
}
}
}
C#如下:
// ---------------------------【均值模糊】---------------------------
using UnityEngine;
//编辑状态下也运行
[ExecuteInEditMode]
//继承自PostEffectsbase
public class BoxBlur : PostEffectsBase {
public Shader myShader;
private Material _material = null;
public Material material {
get {
_material = CheckShaderAndCreateMaterial (myShader, _material);
return _material;
}
}
//模糊半径
[Header("模糊半径")]
[Range (0.2f, 10.0f)]
public float BlurRadius = 1.0f;
//降采样次数
[Header("降采样次数")]
[Range (1, 8)]
public int downSample = 2;
//迭代次数
[Header("迭代次数")]
[Range (0, 4)]
public int iteration = 1;
//-----------------------------------------【Start()函数】---------------------------------------------
void Start () {
//找到当前的Shader文件
myShader = Shader.Find ("lcl/screenEffect/BoxBlur");
}
//-------------------------------------【OnRenderImage函数】------------------------------------
// 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
//--------------------------------------------------------------------------------------------------------
void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture) {
if (material) {
//申请RenderTexture,RT的分辨率按照downSample降低
RenderTexture rt = RenderTexture.GetTemporary (sourceTexture.width >> downSample, sourceTexture.height >> downSample, 0, sourceTexture.format);
//直接将原图拷贝到降分辨率的RT上
Graphics.Blit (sourceTexture, rt);
//进行迭代
for (int i = 0; i < iteration; i++) {
material.SetFloat ("_BlurRadius", BlurRadius);
Graphics.Blit (rt, sourceTexture, material);
Graphics.Blit (sourceTexture, rt, material);
}
//将结果输出
Graphics.Blit (rt, destTexture);
//释放RenderBuffer
RenderTexture.ReleaseTemporary (rt);
}
}
}
高斯模糊:
通过上面的均值模糊的实现,我们可以看到均值模糊的处理效果并不是很好,不够平滑,这时我们就可以用高斯模糊来处理了。
高斯模糊 也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次。
它相对于均值模糊来说,效果更好,但是性能差点,实现起来也相对复杂一点。
并且它之所以叫高斯模糊,正是因为它运用了高斯的正态分布的密度函数!
公式如下:
在图形上表示为:
在图形上,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。
计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。
二维方程则是:
需要特别注意的地方,由于对于图像的处理高斯正态分布是二维的,如果按照正常情况下计算的话,该算法的计算时间复杂度非常高,假设屏幕分辨率是MN,我们的高斯核大小是mn,那么进行一次后处理的时间复杂度为O(MNmn)!
此时我们可以把拆分为两个一维高斯公式来处理,即横线和纵向分别做处理,那么我们在横向方向需要Mmn次采样操作,而在竖向方向需要Nm*n次采样操作,总共的时间复杂度就是O((M+N)mn)
二维高斯公式 推导为 X轴和Y轴的一维公式 如下:
上面的一维公式转换为代码:0.39894*exp(-0.5*x*x/(0.20*sigma))/sigma*sigma
有了该函数就可以分别计算出 垂直 和 水平 方向的 每个点的权重了。
例如:
当 sigma = 5 时,带入公式即可计算出水平方向的每个点的权值了。
假定中心点的坐标是(0,0),那么在水平方向上距离它最近的4个点的坐标如下
为了计算权重,需要设定 sigma 的值。假定 sigma = 5,模糊半径为2,带入以上公式即可算出每个点的权重:
为了计算这5个点的加权平均值,需要对它们做 “归一化” 处理,即每个点的值除以它们的总和,结果如下:
把该权值分别乘以对应点的像素值,再加起来,就可以得到最终的值了,当然这只是水平方向的计算,垂直方向上同理。
具体实现代码如下:
shader:
// ---------------------------【高斯模糊】---------------------------
Shader "lcl/screenEffect/gaussBlur"
{
// ---------------------------【属性】---------------------------
Properties
{
_MainTex("MainTex", 2D) = "white" {}
}
// ---------------------------【子着色器】---------------------------
SubShader
{
//后处理效果一般都是这几个状态
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode Off }
// ---------------------------【渲染通道】---------------------------
Pass
{
// ---------------------------【CG代码】---------------------------
CGPROGRAM
#pragma vertex vert_blur
#pragma fragment frag_blur
#include "UnityCG.cginc"
//顶点着色器输出结构体
struct VertexOutput
{
float4 pos : SV_POSITION; //顶点位置
float2 uv : TEXCOORD0; //纹理坐标
float4 uv01 : TEXCOORD1; //存储两个纹理坐标
float4 uv23 : TEXCOORD2; //存储两个纹理坐标
};
// ---------------------------【变量申明】---------------------------
sampler2D _MainTex;
//纹理中的单像素尺寸
float4 _MainTex_TexelSize;
//给一个offset,这个offset可以在外面设置,是我们设置横向和竖向blur的关键参数
float4 _offsets;
// ---------------------------【顶点着色器】---------------------------
VertexOutput vert_blur(appdata_img v)
{
VertexOutput o;
o.pos = UnityObjectToClipPos(v.vertex);
//uv坐标
o.uv = v.texcoord.xy;
//计算一个偏移值
// offset(1,0,0,0)代表水平方向
// offset(0,1,0,0)表示垂直方向
_offsets *= _MainTex_TexelSize.xyxy;
//由于uv可以存储4个值,所以一个uv保存两个vector坐标,_offsets.xyxy * float4(1,1,-1,-1)可能表示(0,1,0-1),表示像素上下两个
//坐标,也可能是(1,0,-1,0),表示像素左右两个像素点的坐标,下面*2.0,同理
o.uv01 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1);
o.uv23 = v.texcoord.xyxy + _offsets.xyxy * float4(1, 1, -1, -1) * 2.0;
return o;
}
// 计算高斯权重
float computerBluGauss(float x,float sigma) {
return 0.39894*exp(-0.5*x*x/(0.20*sigma))/sigma*sigma;
}
// ---------------------------【片元着色器】---------------------------
fixed4 frag_blur(VertexOutput i) : SV_Target
{
fixed4 color = fixed4(0,0,0,0);
//将像素本身以及像素左右(或者上下,取决于vertex shader传进来的uv坐标)像素值的加权平均
//这里的权值由高斯公式计算而来:
// y = 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma; sigma = 7;
// 例如:
// float w0 = computerBluGauss(0,8);
// float w1 = computerBluGauss(1,8);
// float w2 = computerBluGauss(2,8);
// float sum = w0+w1*2+w2*2;
// color += w0/sum * tex2D(_MainTex, i.uv);
// color += w1/sum * tex2D(_MainTex, i.uv01.xy);
// color += w1/sum * tex2D(_MainTex, i.uv01.zw);
// color += w2/sum * tex2D(_MainTex, i.uv23.xy);
// color += w2/sum * tex2D(_MainTex, i.uv23.zw);
//为了节约性能, 这里就直接取计算后的权值
color += 0.4026 * tex2D(_MainTex, i.uv);
color += 0.2442 * tex2D(_MainTex, i.uv01.xy);
color += 0.2442 * tex2D(_MainTex, i.uv01.zw);
color += 0.0545 * tex2D(_MainTex, i.uv23.xy);
color += 0.0545 * tex2D(_MainTex, i.uv23.zw);
return color;
}
ENDCG
}
}
//后处理效果一般不给fallback,如果不支持,不显示后处理即可
}
C#:
// ---------------------------【高斯模糊】---------------------------
using UnityEngine;
//编辑状态下也运行
[ExecuteInEditMode]
//继承自PostEffectsbase
public class GaussBlur : PostEffectsBase {
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material {
get {
gaussianBlurMaterial = CheckShaderAndCreateMaterial (gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
//模糊半径
[Header("模糊半径")]
[Range (0.2f, 3.0f)]
public float BlurRadius = 1.0f;
//降采样次数
[Header("降采样次数")]
[Range (1, 8)]
public int downSample = 2;
//迭代次数
[Header("迭代次数")]
[Range (0, 4)]
public int iteration = 1;
// [Range (0, 100)]
// public float sigma = 1;
//-----------------------------------------【Start()函数】---------------------------------------------
void Start () {
//找到当前的Shader文件
gaussianBlurShader = Shader.Find ("lcl/screenEffect/gaussBlur");
}
//-------------------------------------【OnRenderImage函数】------------------------------------
// 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
//--------------------------------------------------------------------------------------------------------
void OnRenderImage (RenderTexture source, RenderTexture destination) {
if (material) {
//申请RenderTexture,RT的分辨率按照downSample降低
RenderTexture rt1 = RenderTexture.GetTemporary (source.width >> downSample, source.height >> downSample, 0, source.format);
RenderTexture rt2 = RenderTexture.GetTemporary (source.width >> downSample, source.height >> downSample, 0, source.format);
//直接将原图拷贝到降分辨率的RT上
Graphics.Blit (source, rt1);
//进行迭代高斯模糊
for (int i = 0; i < iteration; i++) {
//第一次高斯模糊,设置offsets,竖向模糊
material.SetVector ("_offsets", new Vector4 (0, BlurRadius, 0, 0));
// material.SetFloat ("_sigma", sigma);
Graphics.Blit (rt1, rt2, material);
//第二次高斯模糊,设置offsets,横向模糊
material.SetVector ("_offsets", new Vector4 (BlurRadius, 0, 0, 0));
// material.SetFloat ("_sigma", sigma);
Graphics.Blit (rt2, rt1, material);
}
//将结果输出
Graphics.Blit (rt1, destination);
//释放申请的RenderBuffer
RenderTexture.ReleaseTemporary (rt1);
RenderTexture.ReleaseTemporary (rt2);
}
}
}
最后看看两种模糊处理的效果对比:
🤣然而我好像看不出什么区别…hhhh
参考文献
https://blog.csdn.net/poem_qianmo/article/details/51871531
https://www.zhihu.com/question/54918332/answer/142137732
https://blog.csdn.net/puppet_master/article/details/52783179