【Shader】常用后处理Shader总结_1

这篇总结一下常用的后处理Shader,套上主Camera就能让游戏画面效果上升一个Level那种,整容效果等同于美图秀秀的滤镜(并没有!)。

后处理的具体实现原理:
1、先把场景里东西给渲染了
2、把渲染好的屏幕图像“截个屏”(我是主要用OnRenderImage)
3、把刚刚的“截屏”进行处理,再放到屏幕上

后处理C#脚本基类

首先写一个万用的后处理C#脚本基类PostEffectsBase。来自于冯乐乐女神的书《UnityShader入门精要》,我吹爆这本书,写的很棒,各种知识点讲的深入浅出,萌新的必备之选,千言万语汇成一句话,乐乐女神就是我偶像!

using UnityEngine;

public class PostEffectsBase : MonoBehaviour
{
    protected void CheckResources()
    {
        bool isSupported = CheckSupport();
        if (!isSupported)
        {
            NotSupported();
        }
    }

    protected bool CheckSupport()//检测平台是否支持后处理
    {
        if (SystemInfo.supportsImageEffects == false)
        {
            Debug.LogWarning("This platform does not support image effects!");
            return false;
        }
        return true;
    }

    protected void NotSupported()
    {
        enabled = false;
    }

    protected void Start()
    {
        CheckResources();        
    }

    protected Material CheckShaderAndMaterial(Shader shader, Material material)
    {
        if (shader == null)
            return null;


        if (shader.isSupported && material != null && material.shader == shader)
            return material;
        else
        {
            material = new Material(shader)
            {
                hideFlags = HideFlags.DontSave
            };
            return material;
        }
    }
}

之后写的各种C#脚本继承自这个基类就可以了。

饱和度/对比度/明度调节

这个是最基础的后处理,非常简单。

主要思路:
1、饱和度,计算当前图像0饱和度的颜色: 0.2125 * r + 0.7154 * g + 0.0721 * b,再把输入图像颜色与该颜色按饱和度进行插值输出
2、对比度,把输入图像颜色与(0.5,0.5,0.5,1.0)的灰色按对比度进行插值输出
3、明度,把输入图像颜色与明度相乘输出

饱和度/对比度/明度调节

Shader

Shader "Custom/BrightnessSaturationAndContrast"
{
	Properties
	{
		_MainTex ("Base(RGB)", 2D) = "white" {}
	}
	SubShader
	{
		Pass
		{
			ZTest Always 
			Cull Off 
			ZWrite Off
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			struct v2f
			{
				float4 pos : SV_POSITION;
				half2 uv : TEXCOORD0;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Brightness;
			float _Saturation;
			float _Contrast;

			v2f vert (appdata_img v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 renderTex = tex2D(_MainTex,i.uv);

			    fixed3 finalColor = renderTex.rgb * _Brightness;//亮度:直接相乘

				fixed luminance = 0.2125 * renderTex.r + 0.7154 *renderTex.g + 0.0721*renderTex.b;
				fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
				finalColor = lerp(luminanceColor, finalColor, _Saturation);//饱和度:将颜色值转化,再进行插值

				fixed3 avgColor = fixed3(0.5, 0.5, 0.5);//对比度:与灰色插值
				finalColor = lerp(avgColor, finalColor, _Contrast);
			
				return fixed4(finalColor, renderTex.a);
			}
			ENDCG
		}
	}
	Fallback Off
}

C#

using UnityEngine;

[ExecuteInEditMode]
public class BrightnessSaturationAndContrast : PostEffectsBase
{
    public Shader briSatConShader;
    private Material briSatConMaterial;
    public Material BriSatConMaterial
    {
        get
        {
            briSatConMaterial = CheckShaderAndMaterial(briSatConShader,briSatConMaterial);
            return briSatConMaterial;
        }
    }

    [Range(0.0f, 3.0f)]
    public float brightness = 1.0f;

    [Range(0.0f, 3.0f)]
    public float saturation = 1.0f;

    [Range(0.0f, 3.0f)]
    public float contrast = 1.0f;

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if(BriSatConMaterial != null)
        {
            BriSatConMaterial.SetFloat("_Brightness", brightness);
            BriSatConMaterial.SetFloat("_Saturation", saturation);
            BriSatConMaterial.SetFloat("_Contrast", contrast);

            Graphics.Blit(src, dest, BriSatConMaterial);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

高斯模糊Gaussian Blur

高斯模糊比上面那个要复杂一点,不过也算比较容易的,而且很多后处理都会用到高斯模糊,所以可以把高斯模糊的Pass命名,在其他后处理Shader中就可以很方便的使用了。

高斯模糊后处理涉及到一点计算机图像处理的知识,要使用高斯滤波来处理输入的屏幕图像再输出。

简而言之就是把3x3像素点范围内的每个像素点颜色,乘上高斯滤波中对应的权重,相加后得到中心像素点的输出颜色,就可以得到模糊后的图像。外圈点里中心点越远,模糊程度越高,通过调整这些点的范围来调整模糊程度。

又因为高斯滤波横向纵向数值相同,所以实际只储存三个值,分别对纵向三个点和横向三个点进行两次计算即可。通过调节Shader里

另外还可以通过重复高斯模糊的次数来调节模糊程度,另外还有对屏幕图像进行降采样来调节模糊程度,这样可以节约性能。

高斯模糊

C#

using UnityEngine;

public class Blur : PostEffectsBase
{
    public Shader shader;
    private Material material;
    public Material Material
    {
        get
        {
            material = CheckShaderAndCreateMaterial(shader, material);
            return material;
        }
    }

    [Range(0, 4.0f)]
    public int iterations = 2;  //模糊迭代次数

    [Range(0, 3.0f)]
    public float blurSpread = 1.0f;  //模糊范围

    [Range(1, 8)]
    public int downSample = 1;       //降采样倍数

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        int height = source.height / downSample;
        int width = source.width / downSample;

        if (Material != null)
        {
            Material.SetInt("_Iterations", iterations);
            Material.SetFloat("_BlurSpread", blurSpread);
            RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
            RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);
            buffer0.filterMode = FilterMode.Bilinear;
            buffer1.filterMode = FilterMode.Bilinear;
            Graphics.Blit(source, buffer0);

            for (int i = 0; i < iterations; i++)
            {
                Material.SetFloat("_BlurSize", i * blurSpread);

                buffer1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer0, buffer1, Material, 0);               
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;

                buffer1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer0, buffer1, Material, 1);                
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }
       
            Graphics.Blit(buffer0, destination);
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

Shader

Shader "Custom/GaussianBlur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		ZTest Always Cull Off ZWrite Off

		CGINCLUDE
		#include "UnityCG.cginc"

		sampler2D _MainTex;
		float2 _MainTex_TexelSize;
		float _BlurSize;

		struct v2f
		{
			float4 pos : SV_POSITION;
			half2 uv[5] : TEXCOORD0;
		};

		v2f vert_Vertical(appdata_img v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			half2 uv = v.texcoord;

			o.uv[0] = uv;
			o.uv[1] = uv + half2(0, _MainTex_TexelSize.y *  1)*_BlurSize;
			o.uv[2] = uv + half2(0, _MainTex_TexelSize.y * -1)*_BlurSize;
			o.uv[3] = uv + half2(0, _MainTex_TexelSize.y *  2)*_BlurSize;
			o.uv[4] = uv + half2(0, _MainTex_TexelSize.y * -2)*_BlurSize;

			return o;
		}

		v2f vert_Horizontal(appdata_img v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			half2 uv = v.texcoord;

			o.uv[0] = uv;
			o.uv[1] = uv + half2(_MainTex_TexelSize.x *  1, 0)*_BlurSize;
			o.uv[2] = uv + half2(_MainTex_TexelSize.x * -1, 0)*_BlurSize;
			o.uv[3] = uv + half2(_MainTex_TexelSize.x *  2, 0)*_BlurSize;
			o.uv[4] = uv + half2(_MainTex_TexelSize.x * -2, 0)*_BlurSize;

			return o;
		}

		fixed4 frag(v2f i) : SV_Target
		{
			const half weights[3] = {0.4026,0.2442,0.0545};
			fixed3 sum = fixed3(0,0,0);

			sum += tex2D(_MainTex, i.uv[0]).rgb * weights[0];
			sum += tex2D(_MainTex, i.uv[1]).rgb * weights[1];
			sum += tex2D(_MainTex, i.uv[2]).rgb * weights[1];
			sum += tex2D(_MainTex, i.uv[3]).rgb * weights[2];
			sum += tex2D(_MainTex, i.uv[4]).rgb * weights[2];

			return fixed4(sum, 1.0);
		}

		ENDCG

		Pass
		{		
			NAME "BLUR_VERTICAL"

			CGPROGRAM
			#pragma vertex vert_Vertical
			#pragma fragment frag						
			ENDCG
		}

		Pass
		{		
			NAME "BLUR_HORIZONTAL"

			CGPROGRAM
			#pragma vertex vert_Horizontal
			#pragma fragment frag						
			ENDCG
		}	
	}
	FallBack Off
}

Bloom

Bloom算是非常常用的一个后处理了。主要原理是先提取屏幕图像亮部,然后对亮度进行高斯模糊,再与原图像混合。就能得到一种模糊光晕的效果,运用很是广泛。

这里的模糊是直接用的上面写的高斯模糊的shader,少写点重复的代码。

(示例参数调狠了,miku后面书架的细节都给糊没了……)
Bloom

C#

using UnityEngine;

public class Bloom : PostEffectsBase
{
    public Shader shader;
    private Material material;
    public Material Material
    {
        get
        {
            material = CheckShaderAndCreateMaterial(shader, material);
            return material;
        }
    }

    [Range(0.0f, 1.0f)]
    public float bloomSize = 0.6f;

    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;  //模糊范围

    [Range(0, 4)]
    public int iterations = 3;  //模糊迭代次数

    [Range(1, 8)]
    public int downSample = 2;       //降采样倍数

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (Material != null)
        {
            int width = source.width/downSample;
            int height = source.height/downSample;

            RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
            buffer0.filterMode = FilterMode.Bilinear;
            RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);

            Material.SetFloat("_BloomSize", bloomSize);
            Graphics.Blit(source, buffer0, Material, 0);

            for (int i = 0; i < iterations; i++)
            {
                Material.SetFloat("_BlurSize", i * blurSpread);

                buffer1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer0, buffer1, Material, 1);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;

                buffer1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer0, buffer1, Material, 2);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }
            Material.SetTexture("_Bloom", buffer0);
            Graphics.Blit(source, destination, Material, 3);
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }


}

Shader

Shader "Custom/Bloom"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		CGINCLUDE

		#include "UnityCG.cginc"

		sampler2D _MainTex;
		half2 _MainTex_TexelSize;
		sampler2D _Bloom;
		float _BlurSize;
		float _BloomSize;

		struct v2f
		{
			float4 pos : SV_POSITION;
			half2 uv : TEXCOORD0;
		};

		v2f vert_Extract(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 frag_Extract(v2f i) : SV_Target
		{
			fixed4 texColor = tex2D(_MainTex,i.uv);
			fixed brightness = clamp(luminance(texColor) - _BloomSize, 0.0 , 1.0);

			return texColor * brightness;
		}

		struct v2fBloom
		{
			float4 pos : SV_POSITION;
			half4 uv : TEXCOORD0;
		};

		v2fBloom vert_Blend(appdata_img v)
		{
			v2fBloom o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv.xy = v.texcoord;
			o.uv.zw = v.texcoord;

			#if UNITY_UV_STARTS_AT_TOP
			if (_MainTex_TexelSize.y < 0)
				o.uv.w = 1.0 - o.uv.w;
			#endif

			return o;
		}

		fixed4 frag_Blend(v2fBloom i) : SV_Target
		{
			return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
		}

		ENDCG

	
		Pass
		{
			CGPROGRAM
			#pragma vertex vert_Extract
			#pragma fragment frag_Extract			
			ENDCG
		}

		UsePass "Custom/GaussianBlur/BLUR_VERTICAL"

		UsePass "Custom/GaussianBlur/BLUR_HORIZONTAL"

		Pass
		{
			CGPROGRAM
			#pragma vertex vert_Blend
			#pragma fragment frag_Blend			
			ENDCG
		}
	}
	Fallback Off
}

Bloom扩展

基于Bloom的思路可以做很多有意思的后处理,比如这个十字光斑

主要思路是先提取亮部,然后分别把纵向模糊和横向模糊结果存在两张RT上,再传给shader,采样后跟原图像混合即可。代码也跟Bloom的大同小异,新加入了一个brightness参数控制亮部的亮度,因为太暗了效果不明显

(Miku那张图整张的亮度主次不是很清,所以用了这张樱花,不过效果也不是太好,适用于那种有小面积亮光的场景,比如透过树叶的缝隙洒下阳光这种)

Bloom_Extra

C#

using UnityEngine;

public class Bloom_Extra : PostEffectsBase
{
    public Shader shader;
    private Material material;
    public Material Material
    {
        get
        {
            material = CheckShaderAndCreateMaterial(shader, material);
            return material;
        }
    }

    [Range(0.0f, 1.0f)]
    public float bloomSize = 0.6f;

    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;  //模糊范围

    [Range(0, 20)]
    public int iterations = 3;  //模糊迭代次数

    [Range(1, 8)]
    public int downSample = 2;       //降采样倍数

    [Range(0, 10)]
    public float brightness = 0.5f;   //控制亮部亮度

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (Material != null)
        {
            int width = source.width / downSample;
            int height = source.height / downSample;

            RenderTexture buffer0 = RenderTexture.GetTemporary(width, height);
            buffer0.filterMode = FilterMode.Bilinear;
            RenderTexture buffer1 = RenderTexture.GetTemporary(width, height);
            buffer1.filterMode = FilterMode.Bilinear;

            RenderTexture buffer2 = RenderTexture.GetTemporary(width, height);
            RenderTexture buffer3 = RenderTexture.GetTemporary(width, height);

            Material.SetFloat("_BloomSize", bloomSize);
            Material.SetFloat("_Brightness", brightness);

            Graphics.Blit(source, buffer0, Material, 0);
            Graphics.Blit(source, buffer1, Material, 0);

            for (int i = 0; i < iterations; i++)
            {
                Material.SetFloat("_BlurSize", i * blurSpread);

                buffer2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer0, buffer2, Material, 1);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer2;

                buffer3 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(buffer1, buffer3, Material, 2);
                RenderTexture.ReleaseTemporary(buffer1);
                buffer1 = buffer3;
            }
            Material.SetTexture("_Bloom_Vertical", buffer0);
            Material.SetTexture("_Bloom_Horizontal", buffer1);

            Graphics.Blit(source, destination, Material, 3);
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

shader

Shader "Custom/Bloom_Extra"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		CGINCLUDE

		#include "UnityCG.cginc"

		sampler2D _MainTex;
		half2 _MainTex_TexelSize;
		sampler2D _Bloom_Vertical;
		sampler2D _Bloom_Horizontal;		
		float _BlurSize;
		float _BloomSize;
		float _Brightness;

		struct v2f
		{
			float4 pos : SV_POSITION;
			half2 uv : TEXCOORD0;
		};

		v2f vert_Extract(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 frag_Extract(v2f i) : SV_Target
		{
			fixed4 texColor = tex2D(_MainTex,i.uv);
			fixed brightness = clamp(luminance(texColor) - _BloomSize, 0.0 , 1.0);

			return texColor * brightness * _Brightness;
		}

		struct v2fBloom
		{
			float4 pos : SV_POSITION;
			half4 uv : TEXCOORD0;
		};

		v2fBloom vert_Blend(appdata_img v)
		{
			v2fBloom o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv.xy = v.texcoord;
			o.uv.zw = v.texcoord;

			#if UNITY_UV_STARTS_AT_TOP
			if (_MainTex_TexelSize.y < 0)
				o.uv.w = 1.0 - o.uv.w;
			#endif

			return o;
		}

		fixed4 frag_Blend(v2fBloom i) : SV_Target
		{
			return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom_Vertical,i.uv.zw) + tex2D(_Bloom_Horizontal,i.uv.zw);
		}

		ENDCG

	
		Pass
		{
			CGPROGRAM
			#pragma vertex vert_Extract
			#pragma fragment frag_Extract			
			ENDCG
		}

		UsePass "Custom/GaussianBlur/BLUR_VERTICAL"

		UsePass "Custom/GaussianBlur/BLUR_HORIZONTAL"

		Pass
		{
			CGPROGRAM
			#pragma vertex vert_Blend
			#pragma fragment frag_Blend			
			ENDCG
		}
	}
	Fallback Off
}

这篇先写这么多吧,剩下的总结2再写……

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值