本文已参与[新人创作礼]活动,一起开启掘金创作之路
原理:
高斯方程公式如下
其中σ(标准差)一般取值为1;x、y为当前位置到卷积中心的距离,将上述公式带入到5X5的高斯核中,可以得到下面的高斯核m(1~25)
归一化(将每个元素除以上面的和)得到下图高斯核
因此我们只需要利用上面的高斯核计算出包括远点在内的25个点的RBGA的和就是该点新的RBGA。
但是上面需要计算25个点,计算量十分大,我们可以利用高斯方程的降维性质,即
G(x,y)=G(x)×G(y)使原本的渲染可以变成先进行水平方向的(x)渲染,再将渲染过后的进行垂直方向(y)的渲染来降低计算的复杂度
推导过程如下:
因此高斯核可以转化为如下图(同样是利用公式求出对应值,然后再进行归一化)
通过观察可以发现,上述的权重值实际上只有3个(后面代码中会提到)
上代码(代码出自《UnityShader入门精要》)
//Shader部分的代码
Shader "Unity Shaders Book/Chapter 12/Gaussian Blur" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurSize ("Blur Size", Float) = 1.0 //采样间隔
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;//0
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//右1
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//左1
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//右2
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//左2
return o;
}
v2f vertBlurHorizontal(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;//0
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//右1
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//左1
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//右2
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//左2
return o;
}
//取周围及自身共5个点的颜色采样值累加
fixed4 fragBlur(v2f i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};//权重列表(对称的,实际上是5个)
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];// 0
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];//右1、右2
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];//左1、左2
}
return fixed4(sum, 1.0);
}
ENDCG
SubShader {
ZTest Always Cull Off ZWrite Off
Pass {
//命名之后可以重复利用该Pass
NAME "GAUSSIAN_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
}
FallBack "Diffuse"
}
//C#部分的关键代码
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;
Graphics.Blit(src, buffer0);
for (int i = 0; i < iterations; i++) {
//每次图像叠加的像素采样距离
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
//存储垂直渲染后的图片
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// 使用第一个pass(垂直模糊)渲染
Graphics.Blit(buffer0, buffer1, material, 0);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
//存储水平渲染后的图片
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// 使用第二个pass(水平模糊)渲染
Graphics.Blit(buffer0, buffer1, material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Graphics.Blit(buffer0, dest);
RenderTexture.ReleaseTemporary(buffer0);
} else {
Graphics.Blit(src, dest);
}
}
C#部分简单讲解一下: 1、这里的Shader必须搭配C#脚本使用 2、C#脚本中控制模糊的主要有三个变量,分别是**downSample(采样缩小尺寸)、iterations(迭代次数)、blurSpread(模糊扩散),**这三个变量都是值越大,得到的图案就越模糊。 核心: 进行 iterations 的迭代渲染,即 循环{进行shader中的垂直模糊,保存模糊后图片,将模糊后的图片再次进行shader中的水平模糊,保存模糊后图片} ,在模糊后的图片基础上进行多次模糊,并且通过设置 blurSpread ,以达到每次模糊的范围不断扩大的效果
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
还有一个值是 downSample ,采样一个图片的时候后,我们采样的点越少,图片就越模糊
3张gif分别是
1、downSample 由0不断变大
2、iterations 由0不断变大
3、在 iterations=5的情况下,blurSpread 由0不断变大(如果迭代次数≤1,这个值变化是没有用的,原因可以看看C#代码的blurSpread 那条代码 )
完整代码的可以去找《UnityShader入门精要》 冯乐乐版本