高斯模糊的5x5卷积四方格:
如果使用四方格对整个画面像素进行卷积,计算量为:行像素数 * 宽像素数 * 四方格5x5。
因为高斯模糊四方格的特殊性,可以简化成使用两个一维矩阵分别对行像素和列像素进行相乘,计算量为:行像素数 * 宽像素数 * 2 * 2,且效果一样。
同时该一维矩阵中其实只有三个不同的元素。
float weight[3] = {0.4026,0.2442,0.0545};
for (int it = 1 ;it <3 ;it++)
{
sum += tex2D(_MainTex,i.uv[it*2-1]).rgb *weight[it];
sum += tex2D(_MainTex,i.uv[it*2]).rgb *weight[it];
}
利用CGINCLUDE少写一次相同的片元着色器
shader完整代码:
Shader "Custom/GaussianBlurShader"
{
Properties
{
_MainTex("Base(RGB)",2D) = "white"{}
_BlurSize("Blur Size",Float) = 1.0
}
SubShader
{
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;
//uv【0】在中间
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2f vertBlurHorizontal(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
//uv【0】在中间
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.x * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.x * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.x * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.x * 2.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2f i) : SV_Target
{
float weight[3] = {0.4026,0.2442,0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1 ;it <3 ;it++)
{
sum += tex2D(_MainTex,i.uv[it*2-1]).rgb *weight[it];
sum += tex2D(_MainTex,i.uv[it*2]).rgb *weight[it];
}
return fixed4(sum,1.0);
}
ENDCG
ZTest Always Cull Off ZWrite Off
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"
}
脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GaussianBlurCS : PostEffectsBase
{
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material
{
get
{
gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
//调整高斯模糊迭代次数、模糊范围、缩放系数
[Range(0, 4)]
public int iterations = 3;
[Range(0.2f,3.0f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(material !=null)
{
//降采样
int rtW = source.width / downSample;
int rtH = source.height / downSample;
//需要用到两个pass,第一个pass使用竖直方向上的一维滤波核进行滤波,第二个pass使用水平方向上的一维滤波核进行滤波。
//分配一块屏幕图像大小/downSample的缓存区
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
//纹理的过滤模式设置为Bilinear
//Filter Mode(选择纹理在通过 3D 变换拉伸时如何进行过滤):
//纹理放大变模糊,这个过程需要低通滤波过滤。
//Point:像素风格,只采样一个像素
//Bilinear:采样邻近四个像素,进行线性插值混合
buffer0.filterMode = FilterMode.Bilinear;
//将第一个pass中的结果存储到buffer0中
Graphics.Blit(source, buffer0);
//迭代
for(int i = 0;i < iterations; i++)
{
//设置模糊范围
material.SetFloat("_BlurSize", 1.00f + i * blurSpread);
分配一块屏幕图像大小/downSample的缓存区
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//将buffer0为输入,buffer1为输出经过第一个pass
Graphics.Blit(buffer0, buffer1, material, 0);
//释放buffer0
RenderTexture.ReleaseTemporary(buffer0);
//buffer1中的结果赋值给buffer0
buffer0 = buffer1;
//buffer1重新申请一块空间
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//将buffer0为输入,buffer1为输出经过第二个pass
Graphics.Blit(buffer0, buffer1, material, 1);
//释放buffer0
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
//将迭代了几次的buffer0交给结果输出
Graphics.Blit(buffer0, destination);
//释放buffer0
RenderTexture.ReleaseTemporary(buffer0);
}
else
{
//如果没有这个材质则直接输出,没有任何效果
Graphics.Blit(source, destination);
}
}
}
迭代画图画得有点乱,就着代码食用。
效果:
blurSpread 和 downSample 都是出于性能的考虑。在高斯核维数不变的情况下,_BlurSize 越大,模糊程度越高 ,但采样数却不会受到影响。但过大的 _BlurSize 值会造成虚影,这可能并不是我们希望的。而 downSample 越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的 downSample 可能会使图像像素化。