滤波:在图像处理中,通过滤波强调图片的一些特征或去除图片中一些不重要的部分,高斯模糊就是一种滤波方式,它的作用就是让图片平滑的模糊,滤波是一个邻域操作算子,利用给定像素周围的像素值决定此像素的最终输出值
高斯模糊使用高斯核进行卷积运算,也就是对每个像素和其周围像素进行加权平均
图中左侧是一个5×5的高斯核,高斯核中所有的权重值相加为1,且中心权重值大,边缘权重值小,也就是距离越近的像素影响越大
使用一个NxN的高斯核对图像进行卷积滤波,就需要N×N×W×H(W和H是图像的宽和高)次纹理采样,我们可以把二维的高斯核拆成两个一维的高斯核先后对图像进行滤波,得到的结果和使用二维高斯核是一样的,但采样次数只需要2×N×W×H
这两个一维的高斯核是对称的,其中有很多重复的权重,实际只使用了 0.4026,0.2442, 0.0545 这三个权重
实现高斯模糊还需要设置三个参数
代码实现,创建GaussianBlur脚本挂在相机上,用于调整参数
using UnityEngine;
public class GaussianBlur : PostEffectsBase
{public Shader gaussianBlurShader;private Material gaussianBlurMaterial = null;public Material material { get {gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);return gaussianBlurMaterial;} }// 降采样[Range(1, 8)]public int downSample = 2;// 迭代次数[Range(0, 4)]public int iterations = 3;// 模糊范围[Range(0.1f, 3.0f)]public float blurSize = 0.6f;void OnRenderImage (RenderTexture src, RenderTexture dest){if (material == null) {Graphics.Blit(src, dest);return;}int rtW = src.width / downSample;int rtH = src.height / downSample;// 创建一个临时的渲染纹理并赋值给buffer0RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);buffer0.filterMode = FilterMode.Bilinear;// 把src中的图像缩放后存储到buffer0中Graphics.Blit(src, buffer0);for (int i = 0; i < iterations; i++) {material.SetFloat("_BlurSize", 1.0f + i * blurSize);RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// 调用第一个Pass时,输入是buffer0,输出是buffer1,buffer0纹理会被传递给Shader中名为_MainTex的纹理属性Graphics.Blit(buffer0, buffer1, material, 0);// 先把buffer0释放,再把buffer0指向buffer1,重新分配buffer1RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// 调用第二个PassGraphics.Blit(buffer0, buffer1, material, 1);RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;}Graphics.Blit(buffer0, dest);RenderTexture.ReleaseTemporary(buffer0);}
}
shader实现,两个Pass使用竖直方向和水平方向的一维高斯核对图像进行滤波,得到最终的目标图像
Shader "MyCustom/GaussianBlur"
{Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_BlurSize ("Blur Size", Float) = 1.0}SubShader {// CGINCLUDE类似于C++中头文件的功能。由于高斯模糊需要定义两个Pass,但它们使用的片元着色器代码// 是完全相同的,使用CGINCLUDE可以避免我们编写两个完全一样的frag函数。CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex; half4 _MainTex_TexelSize;float _BlurSize;struct v2f{float4 pos : SV_POSITION;half2 uv[5]: TEXCOORD0;};// appdata_img 只包含顶点坐标和uvv2f vertBlurVertical(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);half2 uv = v.texcoord;// 当前的uvo.uv[0] = uv;// 上下1个单位的uv,_BlurSize控制采样距离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;// 上下2个单位的uvo.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;o.uv[0] = uv;o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;return o;}fixed4 fragBlur(v2f i) : SV_Target{float weight[3] = {0.4026, 0.2442, 0.0545};// 当前的uv使用权重weight[0]fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];for (int it = 1; it < 3; it++){//上下1个单位(或左右1个单位)的uv使用权重weight[1]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);}ENDCGZTest AlwaysCull OffZWrite OffPass {//定义名字,可以在其他shader中使用该pass,必须大写NAME "GAUSSIAN_BLUR_VERTICAL"CGPROGRAM#pragma vertex vertBlurVertical #pragma fragment fragBlurENDCG }Pass{ NAME "GAUSSIAN_BLUR_HORIZONTAL"CGPROGRAM#pragma vertex vertBlurHorizontal #pragma fragment fragBlurENDCG}} FallBack "Diffuse"
}
脚本上引用shader,实际效果
用Frame Debug查看渲染过程,第一个和最后一个Pass是用来拷贝贴图的,其他的都是高斯模糊用到的两个Pass,迭代次数加1,就会多两个Pass,这是迭代次数为3的情况
Bloom效果是建立在高斯模糊的基础上的,它让画面中较亮的区域 “扩散” 到周围的区域中,造成一种朦胧的效果
实现过程:
在相机上添加脚本Bloom,内容和高斯模糊差不多,增加了一个参数用于提取较亮区域
using UnityEngine;
public class Bloom : PostEffectsBase
{public Shader bloomShader;private Material bloomMaterial = null;public Material material{ get{bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);return bloomMaterial;} }// 降采样[Range(1, 8)]public int downSample = 2;// 迭代次数[Range(0, 4)]public int iterations = 3;// 模糊范围[Range(0.1f, 3.0f)]public float blurSize = 0.6f;// 图像的亮度值一般不会超过1。但如果我们开启了HDR,硬件会允许我们把// 颜色值存储在一个更高精度范围的缓冲中,此时像素的亮度值可能会超过1[Range(0.0f, 4.0f)]public float luminanceThreshold = 0.6f;void OnRenderImage (RenderTexture src, RenderTexture dest){if (material == null){Graphics.Blit(src, dest);return;}material.SetFloat("_LuminanceThreshold", luminanceThreshold);int rtW = src.width / downSample;int rtH = src.height / downSample;RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);buffer0.filterMode = FilterMode.Bilinear;// Pass0提取图像中的较亮区域,提取得到的较亮区域将存储在buffer0中Graphics.Blit(src, buffer0, material, 0);// Pass1,Pass2利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果for (int i = 0; i < iterations; i++){material.SetFloat("_BlurSize", 1.0f + i * blurSize);RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// 竖直方向对图像进行滤波Graphics.Blit(buffer0, buffer1, material, 1);RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// 水平方向对图像进行滤波Graphics.Blit(buffer0, buffer1, material, 2);RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;}material.SetTexture ("_Bloom", buffer0);// Pass3将buffer0和原图src进行混合Graphics.Blit(src, dest, material, 3);RenderTexture.ReleaseTemporary(buffer0);}
}
shader实现,高斯模糊部分直接引用上面的Pass
Shader "MyCustom/Bloom"
{Properties{_MainTex ("Base (RGB)", 2D) = "white" {}_Bloom ("Bloom (RGB)", 2D) = "black" {} //高斯模糊后的较亮区域_LuminanceThreshold ("Luminance Threshold", Float) = 0.5_BlurSize ("Blur Size", Float) = 1.0}SubShader{CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;half4 _MainTex_TexelSize;sampler2D _Bloom;float _LuminanceThreshold;float _BlurSize;struct v2f{float4 pos : SV_POSITION; half2 uv : TEXCOORD0;}; v2f vertExtractBright(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;return o;}fixed luminance(fixed4 color){return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; }fixed4 fragExtractBright(v2f i) : SV_Target{//提取亮部区域fixed4 c = tex2D(_MainTex, i.uv);fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);return c * val;}struct v2fBloom{float4 pos : SV_POSITION; half4 uv : TEXCOORD0;};v2fBloom vertBloom(appdata_img v){v2fBloom o;o.pos = UnityObjectToClipPos (v.vertex);//xy分量对应了_MainTex,即原图像的纹理坐标。而它的zw分量是_Bloomo.uv.xy = v.texcoord; o.uv.zw = v.texcoord;//对这个纹理坐标进行平台差异化处理#if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0.0)o.uv.w = 1.0 - o.uv.w;#endifreturn o; }fixed4 fragBloom(v2fBloom i) : SV_Target{return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);} ENDCGZTest AlwaysCull OffZWrite OffPass{CGPROGRAM#pragma vertex vertExtractBright#pragma fragment fragExtractBrightENDCG}UsePass "MyCustom/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"UsePass "MyCustom/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"Pass{CGPROGRAM#pragma vertex vertBloom#pragma fragment fragBloomENDCG}}FallBack Off
}
实际效果
用Frame Debug查看渲染过程
这个Pass0处理后的图像,保留了较亮区域,较暗区域为纯黑色
高斯模糊多次迭代后,最后和原图叠加就生成最终效果
《Unity Shader入门精要》
上一篇: 儿童保健工作计划范文
下一篇: 中班年级组工作计划