Shader 高斯模糊和Bloom

高斯模糊

滤波:在图像处理中,通过滤波强调图片的一些特征或去除图片中一些不重要的部分,高斯模糊就是一种滤波方式,它的作用就是让图片平滑的模糊,滤波是一个邻域操作算子,利用给定像素周围的像素值决定此像素的最终输出值

在这里插入图片描述

高斯模糊使用高斯核进行卷积运算,也就是对每个像素和其周围像素进行加权平均

在这里插入图片描述

图中左侧是一个5×5的高斯核,高斯核中所有的权重值相加为1,且中心权重值大,边缘权重值小,也就是距离越近的像素影响越大

使用一个NxN的高斯核对图像进行卷积滤波,就需要N×N×W×H(W和H是图像的宽和高)次纹理采样,我们可以把二维的高斯核拆成两个一维的高斯核先后对图像进行滤波,得到的结果和使用二维高斯核是一样的,但采样次数只需要2×N×W×H

在这里插入图片描述

这两个一维的高斯核是对称的,其中有很多重复的权重,实际只使用了 0.4026,0.2442, 0.0545 这三个权重

实现高斯模糊还需要设置三个参数

  • iterations(迭代次数):进行几次高斯滤波,次数越多,图像越模糊
  • blurSize(模糊范围):blurSize是用来控制高斯滤波器的偏移量的,也就是每个采样点之间的距离。blurSize越大,采样点越分散,模糊范围越大。但是,blurSize过大也会造成虚影的问题
  • downSample(降采样):缩小图像来提高性能,比如1024 x 1024的的图像,降采样为2,图像就被缩小为512 x 512,降采样设置太大可能使图像像素化

代码实现,创建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;
		
		// 创建一个临时的渲染纹理并赋值给buffer0
		RenderTexture 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,重新分配buffer1
			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);
	}
}

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 只包含顶点坐标和uv
		v2f vertBlurVertical(appdata_img v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			
			half2 uv = v.texcoord;
			// 当前的uv
			o.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个单位的uv
			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;
			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);
		}
		    
		ENDCG
		
		ZTest Always
		Cull Off
		ZWrite Off
		
		Pass 
		{
			//定义名字,可以在其他shader中使用该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"
}

在这里插入图片描述
脚本上引用shader,实际效果
在这里插入图片描述

在这里插入图片描述
用Frame Debug查看渲染过程,第一个和最后一个Pass是用来拷贝贴图的,其他的都是高斯模糊用到的两个Pass,迭代次数加1,就会多两个Pass,这是迭代次数为3的情况

Bloom(辉光效果)

Bloom效果是建立在高斯模糊的基础上的,它让画面中较亮的区域 “扩散” 到周围的区域中,造成一种朦胧的效果
实现过程:

  1. 根据一个阈值提取出图像中的较亮区域,把它们存储在一张RT中
  2. 利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果
  3. 将这张RT和原图像进行混合,得到最终的效果

在相机上添加脚本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分量是_Bloom
			o.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;
			#endif
				        	
			return o; 
		}
		
		fixed4 fragBloom(v2fBloom i) : SV_Target
		{
			return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);
		} 
		ENDCG
		
		ZTest Always
		Cull Off
		ZWrite Off
		
		Pass
		{
			CGPROGRAM
			#pragma vertex vertExtractBright
			#pragma fragment fragExtractBright
			ENDCG
		}
		
		UsePass "MyCustom/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
		UsePass "MyCustom/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"
		
		Pass
		{
			CGPROGRAM
			#pragma vertex vertBloom
			#pragma fragment fragBloom
			ENDCG
		}
	}
	FallBack Off
}

实际效果
在这里插入图片描述
用Frame Debug查看渲染过程
在这里插入图片描述
这个Pass0处理后的图像,保留了较亮区域,较暗区域为纯黑色

在这里插入图片描述
高斯模糊多次迭代后,最后和原图叠加就生成最终效果

参考

《Unity Shader入门精要》
【技术美术百人计划】图形 4.1 Bloom算法 游戏中的辉光效果实现

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值