Shader笔记十四 屏幕后处理效果

屏幕后处理是指渲染完整个场景得到屏幕图像后,再对屏幕图像做处理,实现屏幕特效。

实现屏幕后处理效果的关键在于得到渲染后的屏幕图像,Unity提供了对应的接口 OnRenderImage ,其函数声明

MonoBehaviour.OnRenderImage(RnederTexture src, RenderTexture dest)   

参数 src :源纹理,用于存储当前渲染的得到的屏幕图像
参数 dest :目标纹理,经过一系列操作后,用于显示到屏幕的图像

在OnRenderImage函数中,通常调用 Graphics.Blit函数 完成对渲染纹理的处理

public static void Blit(Texture src,RenderTexture dest);     
public static void Blit(Texture src, RenderTexture dest, Material mat, int pass=-1);
public static void Blit(Texture src,Material mat, int pass=-1);   

参数 src :源纹理 通常指当前屏幕渲染纹理或上一步处理得到的纹理
参数 dest : 目标渲染纹理,如果值为null,则会直接将结果显示到屏幕上
参数 mat : 使用的材质,该材质使用的Unity Shader将会进行各种屏幕后处理操作, src对应的纹理会传递给Shader中_MainTex的纹理属性
参数 pass:默认值为-1,表示依次调用Shader内所有Pass,否则调用索引指定的Pass

默认情况下,OnRenderImage 函数会在所有不透明和透明Pass执行完后被调用,若想在不透明Pass执行完后调用,即不对透明物体产生影响,可以在OnRenderImage函数前添加ImageEffectOpaque的属性实现。

Unity中实现屏幕后处理出效果,通常步骤:

  1. 在摄像机添加屏幕后处理脚本,该脚本中会实现OnRenderImage函数获取当前屏幕渲染纹理
  2. 调用Graphics.Blit函数使用特定Shader对当前图像进行处理,再将最终目标纹理渲染到屏幕上。对于复杂的后处理特效,需要多次调用Graphics.Blit函数

在进行屏幕后处理前,需要检查是否满足后处理条件,创建一个用于屏幕后处理效果的基类,在实现屏幕后处理效果时,只需继承该基类,再实现派生类中的具体操作,完整代码:

[ExecuteInEditMode]
[RequireComponent(typeof(Camera)]
public class PostEffectsBase : MonoBehaviour {
protected void CheckResources() {
    bool isSupported = CheckSupport();
    if (isSupported == false) {
        NotSupport();
    }
}

protected bool CheckSupport() {
    if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
        return false;
    }
    return true;
}

protected void NotSupport() {
    enabled = false;
}
// Use this for initialization
void Start () {
    //检查资源和条件是否支持屏幕后处理
    CheckResources();  
}

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;
    }
}
}

调整屏幕的亮度、饱和度和对比度
摄像机脚本:

public class BrightnessSaturationAndContrast : PostEffectsBase{

public Shader briSatConShader;
private Material briSatConMaterial;

public Material material {
    get {
        briSatConMaterial = CheckShaderAndCreateMaterial(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 (material != null)
    {
        material.SetFloat("_Brightness", brightness);
        material.SetFloat("_Saturation", saturation);
        material.SetFloat("_Contrast", contrast);
        //若材质可用,将参数传递给材质,在调用Graphics.Blit进行处理
        Graphics.Blit(src, dest, material);
    }
    else {
        //否则将图像直接输出到屏幕,不做任何处理
        Graphics.Blit(src, dest);
    }
}
}    

继承自屏幕处理基类PostEffectsBase,指定shader,并根据该shader创建新的材质,通过OnRenderImage方法和Graphics.Blit方法将参数传递到shader中,完成着色,shader代码:

Shader "Custom/Chapter12_BrightnessSaturateAndContrast" {
Properties{
	_MainTex("Maintex",2D)="white"{}
	_Brightness("Brightness",Float)=1
	_Saturation("Saturation",Float)=1
	_Contrast("Contrast",Float)=1

	//Graphics.Blit(src,dest,material)会将第一个参数传递给Shader中名为_MainTex的属性
}
SubShader{
	Pass{
		ZTest Always
		Cull Off
		ZWrite Off

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

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

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

		//appdata_img为Unity内置的结构体,只包含图像处理必须的顶点坐标和纹理坐标
		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
}    

实现效果:
调节前:

调节后:

摄像机脚本参数设置:

边缘检测效果
屏幕后处理中的边缘检测是利用一些边缘检测算子对图像中的像素进行卷积操作,关于图像处理中的卷积计算,可以参考这篇博客(http://www.cnblogs.com/freeblues/p/5738987.html
常见的边缘检测算子有:

边缘检测算子包含两个方向的卷积核,用来计算水平方向和竖直方向的梯度值,得到两个方向的梯度值,而整体的梯度值可以是两个方向上的梯度值平方和开根,为了节约性能可以是两个方向梯度值的绝对值求和,整体梯度的值越大,说明该像素点则越有可能是边缘位置。

边缘检测过程中主要是获取中心像素点及周围的像素点的值,再与边缘检测算子做乘积,计算出对应的梯度,再通过梯度进行插值 完整代码:

Shader "Custom/Chapter12_EdgeDetetion" {
Properties{
	_MainTex("MainTex",2D)="white"{}
	_EdgeOnly("Edge Only",Float)=1.0
	_EdgeColor("Edge Color",Color)=(0,0,0,1)
	_BackgroundColor("BackgroundColor",Color)=(1,1,1,1)
}
SubShader{
	Pass{
		ZTest Always
		Cull Off
		ZWrite Off

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		sampler2D _MainTex;
		float4 _MainTex_ST;
		half4 _MainTex_TexelSize;
		float _EdgeOnly;
		fixed4 _EdgeColor;
		fixed4 _BackgroundColor;

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

		v2f vert(appdata_img v){
			v2f o;
			o.pos=UnityObjectToClipPos(v.vertex);
			half2 uv=v.texcoord;
			//中线像素点及周围8个像素点的UV坐标
			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 color.r*0.2125+color.g*0.7154+color.b*0.0721;
		}
		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;
			half edgeY;
			for(int it=0;it<9;it++){
				texColor=luminance(tex2D(_MainTex,i.uv[it]));
				edgeX+=texColor*Gx[it];
				edgeY+=texColor*Gy[it];
			}
			half edge=1-abs(edgeX)-abs(edgeY);

			return edge;
		}

		fixed4 frag(v2f i):SV_Target{
			half edge=Sobel(i);

			fixed4 withEdgeColor=lerp(_EdgeColor,tex2D(_MainTex,i.uv[4]),edge);
			fixed4 withBackGroundColor=lerp(_EdgeColor,_BackgroundColor,edge);
			//非边缘的像素点withEdgeColor插值结果靠近原有颜色
			//而withBackGroundColor的插值结果就靠近给定的背景色
			return lerp(withEdgeColor,withBackGroundColor,_EdgeOnly);
			//最后的返回结果,通过_EdgeOnly来混合,
			//来控制处理后的图像非边缘的颜色是靠近原颜色还是给定的背景色
		}
		ENDCG
	}
}
FallBack Off
}   

实例效果:

高斯模糊
模糊实现的方式有多种,有均值模糊,中值模糊。均值模糊也会使用图像卷积操作,使用的卷积核的各个元素值都相等,相加的和为1,卷积后得到的像素值是周围像素值的平均值。而中值模糊是选择周围领域内的像素值并对其排序后选择中间值作为返回结果替换原来的颜色。
高斯模糊是通过使用高斯核对周围像素进行计算,高斯核中的元素值并不是相等的而是满足正态分布,基于高斯方程,并且元素值进行归一化操作,使每个权重值除以所有的权重值的和,即所有的权重值相加的和为1,这样处理后的图像不会变暗。关于高斯核,这里有一篇详细介绍的博客http://www.cnblogs.com/zxj015/archive/2013/05/12/3074612.html
高斯核的维数越高,处理后的图像没模糊程度会越高。当使用一个NxN的高斯核对一个WxH的图像进行处理,那么对像素值的采样结果为NxNxWxH次,N越大采样的次数就越大。二维的高斯核可以拆成两个一维的高斯核,得到的结果和直接使用二维高斯核的结果是一样的,这样可以使采样次数降低到2xNxWxH次。而两个一维的高斯核中权重值有重复的权重值,例如一个5x5的一维高斯核只需要记录三个权重值。

后续将使用该高斯核实现模糊效果,实现过程中将会使用两个Pass,第一个使用竖直方向的高斯核对图像进行处理,第二个使用水平方向的高斯核对图像进行处理。同时会增加降采样及应用次数(迭代次数)控制模糊程度。

完整代码:

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

void OnRenderImage(RenderTexture src,RenderTexture dest)
{
    //最简单的处理
    //if (material != null)
    //{
    //    int rtW = src.width;
    //    int rtH = src.height;
    //    RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
    //    //使用RenderTexture.GetTemporary()函数分配一块与屏幕图像大小相同的缓冲区
    //    //由于高斯模糊需要使用两个Pass,而第一个Pass的结果就放在这个缓冲区内保存
    //    Graphics.Blit(src, buffer, material, 0);
    //    Graphics.Blit(buffer, dest, material, 1);

    //    RenderTexture.ReleaseTemporary(buffer);
    //}
    //else
    //{
    //    Graphics.Blit(src,dest);
    //}  

    //增加降采样的处理
    //if (material != null)
    //{
    //    int rtW = src.width/downSample;
    //    int rtH = src.height/downSample;
    //    //增加降采样 
    //    RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);

    //    Graphics.Blit(src, buffer, material, 0);
    //    Graphics.Blit(buffer, dest, material, 1);

    //    RenderTexture.ReleaseTemporary(buffer);
    //}
    //else
    //{
    //    Graphics.Blit(src,dest);
    //}


    //增加降采样处理及迭代的影响
    if (material != null)
    {
        int rtW = src.width/downSample;
        int rtH = src.height/downSample;
        RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
        buffer0.filterMode = FilterMode.Bilinear;

        Graphics.Blit(src, buffer0);
        for (int i = 0; i < iterations; i++)
        {
            material.SetFloat("_BlurSize", 1.0f + i*blurSpread);
            //_BlurSize用来控制采样的距离,在n次迭代下,每次迭代会计算向外一圈的采样结果,
            //再进行高斯核的横向和纵向的计算,结果也就会更加模糊
            RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            Graphics.Blit(buffer0, buffer1, material, 0);
            RenderTexture.ReleaseTemporary(buffer0);
            buffer0 = buffer1;
            buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            Graphics.Blit(buffer0, buffer1, material, 1);
            RenderTexture.ReleaseTemporary(buffer0);
            buffer0 = buffer1;

        }
        Graphics.Blit(buffer0, dest);
        RenderTexture.ReleaseTemporary(buffer0);
    }
    else
    {
        Graphics.Blit(src,dest);
    }
}
}

Shader部分:

Shader "Custom/Chapter12_GaussianBlur" {
Properties {
	_MainTex("MainTex",2D)="white"{}
	_BlurSize("BlurSize",Float)=1.0
}
SubShader {

	
	CGINCLUDE          //使用时,在Pass中直接指定需要使用的着色器函数,避免编写完一样的片元着色器函数
		
	#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;
		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*2.0)*_BlurSize;
		o.uv[3]=uv-float2(0.0,_MainTex_TexelSize.y*1.0)*_BlurSize;
		o.uv[4]=uv-float2(0.0,_MainTex_TexelSize.y*2.0)*_BlurSize;

		return o;
	}

	v2f vertBlurHorizantal(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*2.0,0.0)*_BlurSize;
		o.uv[3]=uv-float2(_MainTex_TexelSize.x*1.0,0.0)*_BlurSize;
		o.uv[4]=uv-float2(_MainTex_TexelSize.x*2.0,0.0)*_BlurSize;

		return o;
	}

	fixed4 frag(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])*weight[it];
			sum+=tex2D(_MainTex,i.uv[it+2])*weight[it];
		}

		return fixed4(sum,1.0);
	}
	ENDCG

	ZTest Always
	ZWrite Off
	Cull Off   

	Pass{
		NAME "GAUSSIAN_BLUR_VERTICAL"
		CGPROGRAM
			#pragma vertex vertBlurVertical
			#pragma fragment frag	
		ENDCG
	}

	Pass{
		NAME "GAUSSIAN_BLUR_HORIZANTAL"
		CGPROGRAM
			#pragma vertex vertBlurHorizantal
			#pragma fragment frag	
		ENDCG
	}
}
FallBack Off
}

实例效果:
原图:

模糊效果:

参数设置

Bloom效果
Bloom特效在游戏中应用比较常见,可以模拟真实摄像机的一种图像效果,使画面中较亮的区域“扩散”到周围的区域,造成朦胧的效果。
Bloom实现原理:
先根据一定的阈值提取图像中较亮的区域,并存储到一张渲染纹理中,然后对该渲染纹理做模糊处理,最后再将该渲染纹理与原纹理进行混合。
完整代码:

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

[Range(0, 4)]
public int iterations = 3;
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
[Range(0.0f, 4.0f)]
public float luminanceThreshold = 0.6f;

void OnRenderImage(RenderTexture src, RenderTexture dest) {
    if (material != null)
    {
        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;
        Graphics.Blit(src, buffer0, material, 0);

        for (int it = 0; it < iterations; it++)
        {
            material.SetFloat("_BlurSize", 1.0f + it * blurSpread);
            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("_BloomTex", buffer0);
        Graphics.Blit(src, dest, material, 3);
        RenderTexture.ReleaseTemporary(buffer0);
    }
    else {
        Graphics.Blit(src,dest);
    }
}
}   

Shader部分:

Shader "Custom/Chapter12_Bloom" {
Properties{
	_MainTex("MainTex",2D)="white"{}
	_BloomTex("BloomTex",2D)="white"{}
	_LuminanceThreshold("LuminanceThreshold",Float)=0.5
	_BlurSize("BlurSize",Float)=1.0
}
SubShader{
	CGINCLUDE
	#include "UnityCG.cginc"
	sampler2D _MainTex;
	float4 _MainTex_TexelSize;
	sampler2D _BloomTex;
	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);
		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(_BloomTex,i.uv.zw);
	}
	ENDCG 

	ZWrite Off 
	ZTest Always
	Cull Off
	Pass{
		CGPROGRAM
			#pragma vertex vertExtractBright
			#pragma fragment fragExtractBright
		ENDCG
	}
	UsePass "Custom/Chapter12_GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
	UsePass "Custom/Chapter12_GaussianBlur/GAUSSIAN_BLUR_HORIZANTAL"
	Pass{
		CGPROGRAM
			#pragma vertex vertBloom
			#pragma fragment fragBloom
		ENDCG
	}
}
FallBack Off
}  

实例效果:
原图:

Bloom效果:

参数设置:

运动模糊
运动模糊是真实世界中的摄像机的一种效果。如果摄像机在曝光时,场景发生变化,就会产生模糊的画面。计算机产生的图像由于不存在曝光,渲染出来的图像往往是线条清晰,缺少运动模糊。
运动模糊的实现有多种方式

  • 利用积累缓存混合多张连续图片
    当物体移动产生多张图片后,取这些图片的平均值作为最后的运动模糊图像。这种方式对性能消耗有较大影响,获取多张帧图像需要在同一帧内多次进行场景渲染。
  • 使用速度缓存
    这个缓存中存储各个像素的运动速度,使用该值决定模糊的方向和大小。

这里使用类似第一种方法实现运动模糊,同一帧中不需要对场景渲染多次,而是保存当前结果,然后将当前结果叠加到之前的渲染图像中,产生类似拖尾的运动轨迹视觉效果。
实例代码:

public class MotionBlur : PostEffectsBase
{
public Shader motionBlurShader;
private Material motionBlurMaterial;

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

//运动模糊在混合图像时使用的模糊参数
[Range(0.0f, 0.9f)]
public float blurAmount = 0.5f;

private RenderTexture accumulationTexture;

//当脚本不运行时,销毁accumulationTure,目的是下一次开始应用运动模糊时重新叠加图像
void OnDisable()
{
    DestroyImmediate(accumulationTexture);
}

void OnRenderImage(RenderTexture src,RenderTexture dest)
{
    if (material != null)
    {
        if (accumulationTexture == null || accumulationTexture.width != src.width ||
            accumulationTexture.height != src.height)
        {
            DestroyImmediate(accumulationTexture);
            accumulationTexture = new RenderTexture(src.width, src.height, 0);
            accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
            //这里由于自己控制该变量的销毁,因此将hideFlags设置为HideAndDontSave,意味着该变量
            //不会显示在Hierarchy,也不会保存到场景中
            Graphics.Blit(src, accumulationTexture);
        }

        accumulationTexture.MarkRestoreExpected();
        //这里使用MarkRestoreExpected()方法表明需要进行一个渲染纹理的恢复操作
        //恢复操作发生在渲染到纹理而该纹理没有被提前清空或销毁的情况下,每次调用OnRenderImage()时需要把当前
        //的帧图像和accumulationTexture中的图像混合,accumulationTexture不需要提前清空,因为它保存了之前的混合结果  

        material.SetFloat("_BlurAmount", 1.0f - blurAmount);
        Graphics.Blit(src, accumulationTexture, material);
        Graphics.Blit(accumulationTexture, dest);
    }
    else
    {
        Graphics.Blit(src,dest);
    }
}
}

Shader代码:

Shader "Custom/Chapter12_MotionBlur" {
Properties{
	_MainTex("Maintex",2D)="white"{}
	_BlurAmount("BlurAmount",Float)=1.0
}
SubShader{
	CGINCLUDE
		#include "UnityCG.cginc"  
		
		sampler2D _MainTex;
		float _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;
		}

		//对当前图像进行采样,将其A通道的值设为_BlurAmount,在后续混合时可以使用透明通道进行混合
		fixed4 fragRGB(v2f i):SV_Target{
			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
	//这里将RGB和A通道分开,是由于在做混合时,按照_BlurAmount参数值将源图像和目标图像进行混合
	//而同时不让其纹理受到A通道值的影响,只是用来做混合,不改变其透明度
		Pass{
			Blend SrcAlpha OneMinusSrcAlpha
			ColorMask RGB
			CGPROGRAM
				#pragma vertex vert
				#pragma fragment fragRGB
			ENDCG
		}
		Pass{
			Blend One Zero
			ColorMask A
			CGPROGRAM
				#pragma vertex vert
				#pragma fragment fragA
			ENDCG
		}
}
}

实例效果:
原场景:

运动模糊效果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值