【Shader入门精要】第十二章——屏幕后处理效果

目录

 

C#相关脚本:

1. 一个用于控制渲染的基类

一、调整屏幕的亮度、饱和度和对比度

二、边缘检测

三、高斯模糊

四、Bloom效果

五、运动模糊

要给自己足够的耐心去学习Shader!


Shader入门精要项目资源:

https://github.com/candycat1992/Unity_Shaders_Book

C#相关脚本:

1. 一个用于控制渲染的基类

作用:检查系统是否支持图像特效、渲染纹理,提供API检查或创建Material、并制定Shader

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class M_PostEffectsBase : MonoBehaviour
{

    protected void Start()
    {
        CheckResources();
    }
    protected void CheckResources()
    {
        bool isSupported = CheckSupport();
        if (isSupported == false)
        {
            NotSupported();
        }
    }

    protected bool CheckSupport()
    {
        if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false)
        {
            Debug.LogWarning("This platform does not support image effects or render textures.");
            return false;
        }
        return true;
    }

    protected void NotSupported()
    {
        enabled = false;
    }

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

一、调整屏幕的亮度、饱和度和对比度

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class M_BrightnessSaturationAndContrast : M_PostEffectsBase
{
    public Shader briSatConShader;
    private Material briSatConMaterial;
    public Material material
    {
        get
        {
            briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
            return briSatConMaterial;
        }
    }
    [Range(0f,3f)]
    public float brightness = 1.0f;//亮度

    [Range(0f, 3f)]
    public float saturation = 1.0f;//饱和度

    [Range(0f, 3f)]
    public float contrast = 1.0f;//对比度

    //source和destination是UNITY传递的屏幕图像(渲染纹理),它们是一样的!
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(material != null)
        {
            material.SetFloat("_Brightness", brightness);
            material.SetFloat("_Saturation", saturation);
            material.SetFloat("_Contrast", contrast);
            //使用这个函数进行将source传入material的_MainTex(必须保证有这个字段)纹理进行用material的着色器渲染,
            Graphics.Blit(source, destination, material);//第三个参数是material材质,还用它身上的着色器shader进行渲染
        }
        else
        {
            //否则,只是将原色source图像输出到屏幕,实际上是啥都没干。因为没有着色器传入(第三个参数material身上的着色器)
            Graphics.Blit(source, destination);
        }
    }
}
Shader "MilkShader/Twently/BrightnewssSaturationAndContrast"
{
	Properties
	{
		_MainTex ("Base(RGB)", 2D) = "white" {}
		//C#传递的参数,分别赌赢 亮度、饱和度、对比度
		_Brightness("Brightnewss", Float) = 1
		_Saturation("Saturation", Float) = 1		
		_Contrast("Contrast", Float) = 1
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }		
		LOD 100

		Pass
		{
			//必须要这样子写!这就是屏幕后处理标配
			ZTest Always Cull Off ZWrite Off
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"			

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

			sampler2D _MainTex;
			float4 _MainTex_ST;
			half _Brightness;
			half _Saturation;
			half _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);
				//首先原色RGB*亮度值 就拿到了亮度值影响后的颜色
				fixed3 finalColor = renderTex.rgb * _Brightness;				
				//用一些特殊的浮点数进行乘以原图的r,g,b值再相加 得到一个特殊的值
				fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
				//用这个特殊值构成了一个饱和度为0的颜色
				fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
				//_Saturation是饱和度,为0时则是饱和度为0的颜色值,否则越接近上面处理后的颜色(亮度影响后的)
				finalColor = lerp(luminanceColor, finalColor, _Saturation);

				//对比度为0的颜色值
				fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
				//_Contrast为0时,拿到的是对比度为0的颜色值,否则越接近上面处理后的颜色(亮度影响后+饱和度影响后的)
				finalColor = lerp(avgColor, finalColor, _Contrast);
				
				//输出,A通道采用原图A通道值
				return fixed4(finalColor, renderTex.a);
			}
			ENDCG
		}
	}
	Fallback Off
}

上面,写了2个脚本,一个是C#,C#脚本是用于控制Shader的参数和启动渲染的,如下方法:

private void OnRenderImage(RenderTexture source, RenderTexture destination)

这个是每帧渲染时执行,会传递入2个参数,第一个source是屏幕渲染纹理,第二个destination是目标渲染纹理。

Graphics.Blit(source, destination, material);

Graphics.Blit方法(原图,目标,材质球),将原图传入材质球身上shader的_MainTex(2D)字段,然后用这个shader进行渲染,它会将结果输出到destination中,注意:它会执行所有Pass,进行渲染,相当于第四个参数为-1

Graphics.Blit(原图,目标)不进行任何处理,直接将原图输出到目标上,即屏幕上。

还有第三个重载:Graphics.Blit(原图,目标,材质,指定PASS索引),第四个参数是指定shader中的第几个Pass进行处理,索引是0开始。如:0 就是第一个Pass。 即Shader当前起作用的SubShader的第一个Pass进行渲染,其他Pass都不会执行!所以这就是可以控制每一个Pass的执行顺序;而如果这个值为-1,则会按顺序执行所有Pass。

最终把C#脚本挂到摄像机身上

图片随便你找一张,因为这是对屏幕处理的,而不是对图片本身做处理,图片身上没有任何的shader处理!!反正就是对屏幕像素点的处理了,你会问shader主纹理是谁传递的?请看上方红色的文字。

二、边缘检测

目前,我使用玄学学习法,即不了解具体算法,只是知道其中的输入和输出即可,效果对了就好。

一样地我们需要一个C#脚本和一个Shader脚本,C#用来控制渲染,传Shader参数,Shader用来处理屏幕像素点;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class M_EdgeDetection : M_PostEffectsBase
{
    public Shader edgeDetectShader;
    private Material edgeDetectMaterial = null;
    public Material material
    {
        get
        {
            edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }
    }

    [Range(0f, 1f)]
    public float edgesOnly = 0.0f;//为1时,只有边缘颜色呈现,具体会以gif图演示

    public Color edgeColor = Color.black;//边缘颜色

    public Color backgroundColor = Color.white;//背景颜色

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);

            Graphics.Blit(source, destination, material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MilkShader/Twently/M_EdgeDetection"
{
	Properties
	{
		_MainTex ("Base(RGB)", 2D) = "white" {}
		_EdgeOnly ("Edge Only", Float) = 1.0
		_EdgeColor ("Edge Color", Color) = (0,0,0,1)
		_BackgroundColor ("Background Color", Color) = (1,1,1,1)
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			ZTest Always Cull Off ZWrite Off
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

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

			sampler2D _MainTex;
			float4 _MainTex_ST;
			half4 _MainTex_TexelSize;
			fixed _EdgeOnly;
			fixed4 _EdgeColor;
			fixed4 _BackgroundColor;
			
			v2f vert (appdata_img v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				half2 uv = v.texcoord;

				//可理解取了当前像素的周围像素纹理坐标,其中o.uv[4]是当前像素
				o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
				o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
				o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
				o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
				o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
				o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
				o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
				o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
				o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

				return o;
			}	
			fixed luminance(fixed4 color){
				return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
			}
			//输入一个v2f结构
			half Sobel(v2f i){
			    //横向权重表
				const half Gx[9] = {-1, -2, -1,
						0, 0, 0,
						1, 2, 1};
				//纵向权重表
				const half Gy[9] = {-1 , 0, 1,
						-2, 0, 2,
						-1, 0, 1};
				half texColor;
				//横向梯度值
				half edgeX = 0;
				//纵向梯度值
				half edgeY = 0;
				//遍历周围像素点
				for (int it = 0;it < 9; it++){					
					//将像素点颜色纸传入luminance方法,可理解里面进行了一个特殊的操作,输出了一个像素综合值
					texColor = luminance(tex2D(_MainTex, i.uv[it]));
					edgeX += texColor * Gx[it]; //将像素综合值 乘以 [it]像素点的横向权重 再加到edgeX
					edgeY += texColor * Gy[it]; //同理(纵向)的处理
				}
				//遍历结束后得到的edgeX edgeY就是梯度值,比如横向梯度值越大,说明当前像素点在横向方向来看与它上、下的像素点颜色跨度越大;纵向梯度值同理

				//1-(edgeX绝对值 + edgeY绝对值) = 1 - 当前像素总体表现的梯度值				
				half edge = 1 - abs(edgeX) - abs(edgeY);
				return edge;
			}
			fixed4 frag (v2f i) : SV_Target
			{
				half edge = Sobel(i);
				//如果有看Sobel解释的同学,可以看看这里的注释)
				//1. 为什么 1 - 总体梯度值呢?  因为当 当前颜色 和 周围相差越大时,梯度越大, 此时edge 越小,lerp插值得到的是边缘颜色!
				// 如果,你将lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);改为 lerp(tex2D(_MainTex, i.uv[4], _EdgeColor), edge);
				// 此时,edge 就等于 梯度值, 梯度值越大,说明当前像素点颜色纸和周围颜色相差越大,说明是边缘!因此显示出边缘颜色!!
				// i.uv[4]是当前像素点纹理坐标

				//withEdgeColor:当越接近边缘时,输出的是边缘颜色,否则输出的是原图颜色,中间是这2个颜色的插值结果,以离边缘的远近程度进行插值运算
				fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
				//onlyEdgeColor:当离边缘越近时,输出边缘颜色,否则输出背景纯色颜色,中间是这2个颜色的插值结果,,以离边缘的远近程度进行插值运算
				fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
				//将上方2个颜色进行插值_EdgeOnly,当为1时,则只显示出边缘颜色+背景纯色颜色(注意背景是自定义的纯色图,而不是屏幕背景哦)
				//否则为0时,则是显示出边缘+原图
				return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
			}
		
			ENDCG
		}
	}
	Fallback Off
}

(_EdgeOnly从0~1,边缘颜色黑色,背景颜色白色)

三、高斯模糊

一样的C#,一样的Shader流程!变得只是传递的参数和渲染手法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class M_GaussianBlur : M_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;

    //缩小比例(2代表缩小1/2) 越大越模糊,性能越好,但是会逐渐像素化!
    [Range(1,8)]
    public int downSample = 2;

    普通版本OnRenderImage
    //private void OnRenderImage(RenderTexture source, RenderTexture destination)
    //{
    //    if (material != null)
    //    {
    //        int rwW = source.width;
    //        int rtH = source.height;
    //        RenderTexture buffer = RenderTexture.GetTemporary(rwW, rwW, 0);
    //        //render the vertical pass
    //        Graphics.Blit(source, buffer, material, 0);
    //        //render the horizontal pass
    //        Graphics.Blit(buffer, destination, material, 1);

    //        RenderTexture.ReleaseTemporary(buffer);
    //    }
    //    else
    //    {
    //        Graphics.Blit(source, destination);
    //    }
    //}

    中级版本OnRenderImage
    //private void OnRenderImage(RenderTexture source, RenderTexture destination)
    //{
    //    if (material != null)
    //    {
    //        //downSample越大性能越好,但过大会导致图像像素化
    //        int rwW = source.width/downSample;
    //        int rtH = source.height/downSample;
    //        //采用降采样(即讲缓存图片大小变为原图的几分之一) 在Pass中进行处理时我们需要处理的像素个数就是原来的几分之一!
    //        RenderTexture buffer = RenderTexture.GetTemporary(rwW, rwW, 0);
    //        //双线性滤波模式
    //        buffer.filterMode = FilterMode.Bilinear;
    //        //render the vertical pass
    //        Graphics.Blit(source, buffer, material, 0);
    //        //render the horizontal pass
    //        Graphics.Blit(buffer, destination, material, 1);

    //        RenderTexture.ReleaseTemporary(buffer);
    //    }
    //    else
    //    {
    //        Graphics.Blit(source, destination);
    //    }
    //}

    //高级版本OnRenderImage
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //downSample越大性能越好,但过大会导致图像像素化
            int rwW = source.width / downSample;
            int rtH = source.height / downSample;
            //采用降采样(即讲缓存图片大小变为原图的几分之一) 在(第一次)Pass中进行处理时我们需要处理的像素个数就是原来的几分之一!
            RenderTexture buffer0 = RenderTexture.GetTemporary(rwW, rwW, 0);
            //双线性滤波模式
            buffer0.filterMode = FilterMode.Bilinear;            

            Graphics.Blit(source, buffer0);//将屏幕图像渲染到buffer0

            //开始iterations次渲染,每次都会进行2次Pass操作:横向、纵向(谁先谁后都无所谓)
            for (int i = 0; i < iterations; i++)
            {
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);//指定采样间距,逐级递增的形式

                RenderTexture buffer1 = RenderTexture.GetTemporary(rwW, rtH, 0);//创建临时buffer1纹理

                //render the vertical pass
                Graphics.Blit(buffer0, buffer1, material, 0); //指定shader第一个pass进行纵向渲染模糊,结果存入buffer1

                RenderTexture.ReleaseTemporary(buffer0);//需要先释放之前的缓存
                buffer0 = buffer1;//buffer0存buffer1数据

                buffer1 = RenderTexture.GetTemporary(rwW, rtH, 0);//再次创建临时buffer1纹理
                //render the horizontal pass
                Graphics.Blit(buffer0, buffer1, material, 1);//指定shader第二个pass进行横向渲染模糊,结果存入buffer1
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }
            //最终结果是保存在buffer0的,我们将它输出到destination
            Graphics.Blit(buffer0, destination);
            //释放buffer0
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MilkShader/Twently/G_GaussianBlur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		//采样间距系数
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader
	{	
		//CGINCLUDE ... ENDCG 是一种组织结构,放在它里面的方法可以被任意Pass直接使用..相当于所有Pass都会有这些内容
		CGINCLUDE		

		#include "UnityCG.cginc"

		sampler2D _MainTex;
		half4 _MainTex_TexelSize;
		float _BlurSize;

		struct v2f
		{
			float4 pos : SV_POSITION;
			half2 uv[5] : TEXCOORD0;
		};
		//我们只需要appdata_img内置结构的数据传入即可(有顶点、纹理坐标)
		v2f vertBlurVertical(appdata_img v){
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			half2 uv = v.texcoord;

			//纵向的5个像素点纹理坐标
			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;

			//横向的5个像素点纹理坐标
			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};
			//采样RGB然后进行乘以对应的权重累加到sum
			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,纵向模糊处理
		Pass
		{
			NAME "GAUSSIAN_BLUR_VERTICAL"

			CGPROGRAM

			//纵向的顶点着色器
			#pragma vertex vertBlurVertical				 
			//片元着色器
			#pragma fragment fragBlur

			ENDCG
		}
		//第二个Pass 横向模糊处理
		Pass
		{
			NAME "GAUSSIAN_BLUR_HORIZONTAL"

			CGPROGRAM

			//横向的顶点着色器
			#pragma vertex vertBlurHorizontal
			#pragma fragment fragBlur

			ENDCG
		}
	}
	Fallback Off
}

(Iterations(0~4变化),Blur Spread为0.6, downScale为2)

(Iterations为3, Blur Spread(0.2~3变化), downScale为2)

(Iterations为3, Blur Spread为0.6, downScale为1~8变化)

具体可自己测试下。

四、Bloom效果

让较亮的区域“扩散”到周围的区域中,造成一种朦胧的效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class M_Bloom : M_PostEffectsBase {

    public Shader bloomShader;
    private Material bloomMaterial = null;
    public Material material
    {
        get
        {
            bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
            return bloomMaterial;
        }
    }

    [Range(0,4)]
    public int iterations = 3;

    [Range(0.2f, 3f)]
    public float blurSpread = 0.6f;

    [Range(1, 8)]
    public int downSample = 2;

    //上面是和高斯模糊一样的参数

    //控制采样较亮区域时使用的阈值大小
    [Range(0f, 4f)]
    public float luminanceThreshold = 0.6f;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            material.SetFloat("_LuminanceThreshold", luminanceThreshold);
            int rtW = source.width / downSample;
            int rtH = source.height / downSample;

            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            buffer0.filterMode = FilterMode.Bilinear;

            //唯一区别在于这里,这里进行了第一次渲染,用的是第一个pass,放入到buffer0
            Graphics.Blit(source, buffer0, material, 0);

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

                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material, 1);//第二个PASS

                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
                Graphics.Blit(buffer0, buffer1, material, 2);//第三个PASS

                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;
            }
            //上面经过三个PASS后,第一个PASS进行的是采样较亮区域并处理后的颜色纸,第二个和第三个是处理经过第一个PASS之后的图像进行了一个高斯模糊
            //然后将结果存入shader的_Bloom字段,经过第四个PASS再次处理输出到屏幕上
            material.SetTexture("_Bloom", buffer0);
            Graphics.Blit(source, destination, material, 3);//第四个PASS

            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MilkShader/Twently/M_Bloom"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Bloom ("Bloom(RGB)", 2D) = "black"{}
		_LuminanceThreshold ("LuminanceThreshold", 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);
			//luminance(c)计算出亮度值 - 阈值 得到改结果,再用其乘以原色, 最终整张图就得到提取后的亮部区域 amazing..神奇
			//如果不懂,可以直接将第四个PASS使用的片元着色器 不进行混合 看下面代码
			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);
			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);
			//return tex2D(_Bloom, i.uv.zw); //注释掉上面的 然后执行这一行,这个_Bloom就是较亮区域的颜色纸输出图(有经过高斯模糊,你也可以去掉高斯模糊再看)
		}		

		ENDCG

		ZTest Always Cull Off ZWrite Off		
		Pass
		{
			CGPROGRAM
			#pragma vertex vertExtractBright	
			#pragma fragment fragExtractBright
			ENDCG
		}
		UsePass "MilkShader/Twently/G_GaussianBlur/GAUSSIAN_BLUR_VERTICAL"//直接用了之前我们写过的一个纵向模糊处理PASS

		UsePass "MilkShader/Twently/G_GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL" //直接用了之前我们写过的一个横向模糊处理PASS

		Pass{
			CGPROGRAM
			#pragma vertex vertBloom
			#pragma fragment fragBloom
			ENDCG
		}
	}
	Fallback Off
}

参数默认如下:

(Luminance Threshold 0~4变化)

五、运动模糊

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class M_MotionBlur : M_PostEffectsBase {

    public Shader motionBlurShader;
    private Material motionBlurMaterial = null;
    public Material material
    {
        get
        {
            motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
            return motionBlurMaterial;
        }
    }

    [Range(0f, 0.9f)]
    public float blurAmount = 0.5f;

    private RenderTexture accumulationTexture;

    private void OnDisable()
    {
        DestroyImmediate(accumulationTexture);//销毁临时纹理
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //确保临时渲染纹理得大小是和屏幕一样得,注意:这个临时渲染纹理是不需要清空的,只有在禁用掉组件时才会销毁
            if(accumulationTexture == null || accumulationTexture.width != source.width || 
                accumulationTexture.height != source.height)
            {
                DestroyImmediate(accumulationTexture);
                accumulationTexture = new RenderTexture(source.width, source.height, 0);
                //不保存和不显示在Hierarchy中
                accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
                //将屏幕图像渲染到临时纹理上
                Graphics.Blit(source, accumulationTexture);
            }
            //进行一个渲染纹理的恢复操作:恢复操作发生在渲染到纹理而该纹理又没有被提前清空或销毁的前提下!这个操作就是我在shader注释所说的
            //进行一个恢复操作,不然上一帧的颜色会一直逗留在屏幕中 你可以试试注释掉行代码
            accumulationTexture.MarkRestoreExpected();

            //当blurAmount越大 传入的值越小,运动模糊效果越显著,因为上一帧的颜色被完全保留下来了,
            //具体看shader代码有详细解释。。。
            material.SetFloat("_BlurAmount", 1.0f - blurAmount);

            //将渲染的结果叠加到临时纹理,注意,还没叠加之前临时纹理还保留着上一帧的渲染结果,然后叠加是指Shader的一个透明度混合操作进行的
            //shader代码用了2个PASS,第一个PASS是只混合RGB通道,A通道不会进行写入到缓冲区,第二个PASS是将原始(最开始的)那个A通道,写入到缓冲区
            Graphics.Blit(source, accumulationTexture, material);
            //临时纹理输出到屏幕
            Graphics.Blit(accumulationTexture, destination);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MilkShader/Twently/M_MotionBlur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BlurAmount ("Blur Amount", Float) = 1.0
	}
	SubShader
	{
		CGINCLUDE
		#include "UnityCG.cginc"

		sampler2D _MainTex;
		fixed _BlurAmount;

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

		v2f vert(appdata_img v){
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = v.texcoord;
			return o;
		}
		fixed4 fragRGB(v2f i) : SV_Target{
			//使用_BlurAmount作为这个片元的输出颜色A通道,在第一个PASS中 我们开启了混合,_BlurAmount就是作为混合因子的
			//即 SrcAlpha 为 _BlurAmount
			// OneMinusSrcAlpha = 1 - _BlurAmount
			// outcolor = sourcecolor * SrcAlpha + destiColor * OneMinusSrcAlpha = sourcecolo * _BlurAmount + destiColor * (1-_BlurAmount)
			//当_BlurAmount越小时,完全保留颜色缓冲区的颜色值,也就是上一帧(A帧)的屏幕颜色被完全保留下来,当然帧(B帧)的颜色会在下一帧进行输出到屏幕。
			//在下一帧(C帧)时的时,UNITY会清空当前帧的上一帧(A帧)的屏幕颜色值或衰减颜色值等处理
			//所以_BlurAmount越小时,越能感觉到运动模糊效果,否则上一帧的颜色纸得不到保留,那就完全没有运动模糊效果
			return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
		}

		half4 fragA(v2f i): SV_Target {
			return tex2D(_MainTex, i.uv);
		}
		ENDCG

		ZTest Always Cull Off ZWrite Off
		Pass
		{
			Blend SrcAlpha OneMinusSrcAlpha
			//只输出RGB到屏幕上
			ColorMask RGB
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment fragRGB
			ENDCG
		}
		Pass
		{
			Blend One Zero
			//只输出A到屏幕上
			ColorMask A
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment fragA
			ENDCG
		}
	}
	Fallback Off
}

要给自己足够的耐心去学习Shader!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值