UnityShader学习笔记 屏幕后处理效果

屏幕后处理脚本系统

想要实现屏幕后处理的基础在于得到渲染后的屏幕图像,即抓取屏幕,Unity提供了一个方便的接口OnRenderImage函数。

而在OnRenderImage函数中通常利用Graphics.Blit函数来完成对渲染纹理的处理。

默认的OnRenderImage函数会在所有的不透明和透明的Pass执行完毕后被调用,但有时希望在不透明的Pass执行完毕后立刻调用,此时我们可以在OnRenderImage函数前添加ImageEffectOpaque属性来实现这样的目的。

要在Unity中实现屏幕后处理效果,过程如下:首先在摄像机中添加一个用于屏幕后处理的脚本,在脚本中实现OnRenderImage函数来获取当前屏幕的渲染纹理。然后再调用Graphics.Blit函数使用特定的Shader来对当前的图像进行处理,再把返回的渲染纹理显示到屏幕上。可能需要多次调用Graphics.Blit函数来对上一步的输出结果进行下一步处理。

在进行屏幕后处理之前我们还需要检查一系列条件是否满足。

屏幕后处理脚本基类如下:

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

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

    protected bool CheckSupport()
    {
        if (SystemInfo.supportsImageEffects == false)
        {
            Debug.Log("平台不支持屏幕效果!!!");
            return false;
        }
        return true;
    }

    protected void NotSupported()
    {
        enabled = false;
    }

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

调整亮度,饱和度和对比度

屏幕后处理脚本部分:

briSatConShader是我们指定的Shader,briSatConMaterial是创建的材质

提供参数调整亮度,饱和度和对比度

在OnRenderImage函数设置用指定的shader创建的material的参数,在调用Graphics.Blit的时候会将第一个参数传递给Shader中名为_MainTex的纹理属性

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

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 Contract = 1.0f;


    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (Material != null)
        {
            Material.SetFloat("_Brightness", Brightness);
            Material.SetFloat("_Saturation", Saturation);
            Material.SetFloat("_Contrast", Contract);

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

Shader部分:

屏幕后处理实际上是在场景中绘制一个与屏幕同宽高的四边形面片,为了防止对其他物体产生影响,我们需要设置相关的渲染状态。

在顶点着色器中只进行了必须的顶点变换。

在片元着色器中实现用于调整亮度、饱和度和对比度的片元着色器。首先得到原屏幕图像(_MainTex)的采样结果renderTex。然后利用_Brightness来调整亮度,亮度的调整时通过把原颜色乘以亮度系数_Brightness即可,然后计算该像素对应的亮度值(luminance),这是通过对每一个颜色分量乘以一个特定的系数再相加得到的,使用该亮度值与另外一个饱和度为0的颜色值通过_Saturation进行插值,得到希望的饱和度颜色,对比度类似,与另外一个对比度为0的颜色值通过_Contrast进行插值得到希望的对比度颜色。

Shader "Custom/BrightnessSaturationAndContrast" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Brightness ("Brightness", Float) = 1
		_Saturation("Saturation", Float) = 1
		_Contrast("Contrast", Float) = 1
	}
	SubShader 
	{
		Pass
		{
			ZTest Always Cull Off ZWrite Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			sampler2D _MainTex;
			half _Brightness;
			half _Saturation;
			half _Contrast;

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

 

边缘检测

屏幕后处理脚本部分:

edgesOnly调整边缘线强度,edgeColor调整描边颜色,backgroundColor调整背景颜色

当edgesOnly为0时,边缘将会叠加在原渲染图像上,当edgesOnly为1时,则只显示边缘,不显示原渲染图像

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

public class EdgeDetection : PostEffectsBase
{
    public Shader EdgeDetectShader;

    private Material edgeDetectMaterial = null;

    public Material material
    {
        get
        {
            edgeDetectMaterial = CheckShaderAndCreateMaterial(EdgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }
    }

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

    public Color edgeColor = Color.black;

    public Color backgroundColor = Color.white;

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);
            Graphics.Blit(src, dest, material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

Shader部分:

 在CG代码块中除了声明与之前的脚本代码中对应的属性外,我们还声明了一个新的变量_MainTex_TexelSize,xxx_TexelSize是Unity为我们提供的访问xxx纹理对应的每个纹素的大小。例如对于一张512x512大小的纹理,该值大约为1/523约为0.001 953。由于卷积需要对相邻区域内的纹理进行采样,因此我们需要利用_MainTex_TexelSize来计算各个相邻区域的纹理坐标。

在顶点着色器中,我们使用Sobel算子采样计算了边缘检测时需要的邻域纹理坐标,并且把计算采样的代码从片元着色器转移到顶点着色器中,减少了运算,提高了性能。

在片元着色器中,首先调用Sobel函数计算当前像素的梯度值edge,并利用该值分别计算了背景为原图和纯色下的颜色值,利用_EdgeOnly在两者之间插值得到最终的像素值。

在Sobel函数中,利用Sobel算子对原图进行边缘检测。首先定义水平方向和竖直方向使用的卷积核Gx和Gy,接着依次对9个像素进行采样计算它们的亮度值,再与卷积和Gx和Gy中对应的权重相乘后,叠加到各自的梯度上,最后用1减去水平方向和竖直方向的梯度值的绝对值,得到edge。edge值越小,表明该位置越可能是一个边缘点。

Shader "Custom/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 
	{
		Pass
		{
			ZTest Always Cull Off ZWrite Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			sampler2D _MainTex;
			half4 _MainTex_TexelSize;
			fixed _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;

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

			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++)
				{
					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 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor,edge);
				return lerp(withEdgeColor, onlyEdgeColor,_EdgeOnly);
			}

			ENDCG
		}
	}
	FallBack Off
}

 

高斯模糊

屏幕后处理部分:

iterations迭代次数

blurSpread模糊范围

downSample缩放系数的参数

第一种实现OnRenderImage函数方式中,

首先利用RenderTexture.GetTemporary函数分配一块与屏幕图像大小相同的缓冲区,这是因为,高斯模糊需要调用两个Pass

第二种实现OnRenderImage的方式如下:

与第一种方式不同的是,在声明缓冲区的大小时使用了小于分辨率的尺寸,并将该渲染纹理的滤波模式修改为双线性。 

对图像进行降采样不仅可以减少需要处理的像素个数,提高性能,而且适当的降采样往往还可以得到更好的模糊效果。

 

最后一种方式还考虑了高斯模糊的迭代次数

 

 

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

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 blueSpread = 0.6f;

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

    void OnRenderImage(RenderTexture src, RenderTexture 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 * blueSpread);

                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部分:

首先声明了各个属性:

_MainTex,需要被处理的纹理,

_BlurSize,模糊程度

 

CGINCLUDE和ENDCG类似C++的头文件的功能,这些语句不需要包含在任何Pass语义块中,在使用时只需要在Pass中直接指定所需要使用的顶点着色器和片元着色器的函数名即可。由于高斯模糊需要定义两个Pass,但片元着色器的代码完全相同,使用代码块可以避免两个完全一样的frag函数。

 

在CG代码块中定义与属性相对应的变量:

_MainTex,_MainTex_TexelSize,_BlurSize,由于要得到相邻像素的纹理坐标,这里再次使用了_MainTex_TexelSize变量计算相邻像素的纹理坐标偏移量

分别定义两个Pass使用的顶点着色器,他们计算的主要区别在于在计算4个纹理坐标时使用了水平还是竖直方向的纹素大小进行纹理偏移的计算

定义两个Pass公用的片元着色器,首先声明各个领域像素对应的权重,然后将结果值初始化为当前像素值乘以它的权重值,并将像素值和权重相乘的结果叠加到sum中,返回滤波结果

 

最后定义两个Pass,为两个Pass使用了NAME语义,这样可以在其他的Shader中直接通过他们的名字来使用该Pass

Shader "Custom/GaussianBlur" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader 
	{
		CGINCLUDE
		
		#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 * 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;

			o.uv[0] = uv;
			o.uv[1] = uv + float2(_MainTex_TexelSize.y * 1.0, 0.0) * _BlurSize;
			o.uv[2] = uv - float2(_MainTex_TexelSize.y * 1.0, 0.0) * _BlurSize;
			o.uv[3] = uv + float2(_MainTex_TexelSize.y * 2.0, 0.0) * _BlurSize;
			o.uv[4] = uv - float2(_MainTex_TexelSize.y * 2.0, 0.0) * _BlurSize;
			return o;
		}

		fixed4 fragBlur(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*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
		{
			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 Off
}

 

Bloom效果

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

实现原理:根据一个阈值提取出图像中的较亮区域,把他们存储在一张渲染纹理中,再利用高斯模糊对这张渲染纹理进行模糊处理,模拟光线扩散的效果,最后再将其和原图像进行混合。

屏幕后处理脚本部分:

由于Bloom效果是建立在高斯模糊的基础上的,只增加了一个新参数luminanceThreshold来控制提取较亮区域时的阈值

在OnRenderImage函数中:

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

public class Bloom : 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, 3.0f)]
    public float blurSpread = 0.6f;

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

    [Range(0.0f, 4.0f)]
    public float luminanceTheshold = 0.6f;

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            material.SetFloat("_LuminanceThreshold", luminanceTheshold);

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

                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);
            Graphics.Blit(src, dest, material, 3);
            RenderTexture.ReleaseTemporary(buffer0);

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

 

Shader部分 :

首先定义提取较亮区域使用的顶点着色器和片元着色器:

在片元着色器中,我们将采样得到的亮度值减去阈值,并截取0-1范围内再与原像素相乘得到提取后的亮部区域

然后定义混合两部图像和原图像时使用的顶点着色器和片元着色器:

在顶点着色器中,定义了两个纹理坐标并存储在同一个类型为half4的变量uv中,它的xy分量对应了_MainTex,zw分量为_Bloom,即模糊后的较亮的纹理坐标

在片元着色器中,只需要将两张纹理的采样结果相加混合即可

接着,定义了Bloom效果需要的4个Pass

最后关闭了Shader的Fallback 

Shader "Custom/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);
			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
		{
			NAME "GAUSSIAN_BLUR_VERTICAL"

			CGPROGRAM
			#pragma vertex vertExtractBright
			#pragma fragment fragExtractBright

			ENDCG
		}

		UsePass "Custom/GaussianBlur/GAUSSIAN_BLUR_VERTICAL"
		UsePass "Custom/GaussianBlur/GAUSSIAN_BLUR_HORIZONTAL"

		Pass
		{
			CGPROGRAM
			#pragma vertex vertBloom
			#pragma fragment fragBloom

			ENDCG
		}
	}
	FallBack Off
}

 

运动模糊

屏幕后处理脚本部分:

blurAmount,运动模糊在混合图像时使用的模糊参数,值越大运动拖尾效果就越明显,但为了防止拖尾效果完全替代当前帧的渲染结果,我们把它的值截取在0到0.9范围内;

RenderTexture保存之前图像叠加的结果;

最后在OnRenderImage函数中:首先创建用于混合图像的满足条件(与当前屏幕分辨率相等)的accumulationTexture,创建完毕后用当前的帧图像初始化accumulationTexture,当得到有效的变量后,调用accumulationTexture.MarkRestoreExpected函数来表明需要一个渲染纹理的恢复操作。恢复操作发生在渲染到纹理而该纹理又没有被提前清空或销毁的情况下。在本例中我们每次调用OnRenderImage时都需要把当前的帧图像和accumulationTexture中的图像混合,accumulationTexture纹理不需要提前清空,因为它保存了我们之前的混合结果。然后,我们将参数传递给材质,并调用Graphic.Blit(scr, accumulationTexture, material)把当前的屏幕图像src叠加到accumulationTexture中,最后使用Graphic.Blit(accumulationTexture, dest)把结果显示到屏幕上。

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

public class MotionBlur : PostEffectsBase
{
    public Shader motionBlurShader;

    private Material motionBlurMaterial = null;

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

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

    private RenderTexture accumulationTexture;

    void Disable()
    {
        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;
                Graphics.Blit(src, accumulationTexture);
            }

            accumulationTexture.MarkRestoreExpected();

            material.SetFloat("_BlurAmount", 1.0f - blurAmount);

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

Shader部分:

声明各个属性:_MainTex对应了输入的渲染纹理,_BlurAmount是混合图像时使用的混合系数,并声明代码中需要使用的各个变量;

顶点着色器中的代码和之前一样:坐标转换,记录uv;

定义两个片元着色器:一个用于更新渲染纹理的RGB通道部分,对当前图像进行采样,并将其A通道的值设为_BlurAmount,以便在后面混合时可以使用它的透明通道进行混合,另一个用于更新渲染纹理的A通道部分,不让其受到混合时使用的透明度值的影响;

定义了两个Pass,一个用于更新渲染纹理的RGB通道,另一个用于更新A通道。在更新RGB时我们需要设置它的A通道来混合图像,但又不希望A通道的值写入渲染纹理中。

Shader "Custom/MotionBlur" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 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
		{
			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
			ColorMask RGB

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment fragRGB

			ENDCG
		}

		Pass
		{
			Blend One Zero
			ColorMask A
			   	
			CGPROGRAM  
			
			#pragma vertex vert  
			#pragma fragment fragA
			  
			ENDCG
		}
	}
	FallBack Off
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值