实现方法:
【1】利用一块 累积缓存(accumulation buffer)来混合多张连续的图像。当物体快速移动产生多张连续的图像后,去取他们之间的平均值作为最后的运动模糊图像,然而这种办法消耗很大
【2】创建和使用 速度缓存(Velocity buffer)这个缓存中存储了各个像素当前的运动速度,然后利用该值来决定模糊的方向和大小
本节使用的是第一种,不过不需要再一帧中把场景渲染多次,但需要保存之前的渲染结果,不断把当前的渲染图像叠加到之前的渲染图像中,从而产生一种运动轨迹的视觉效果,这种方法与原始 的利用累积缓存的方法相比性能更好,但模糊效果可能会略有影响
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//12.6 运动模糊
public class MotionBlur : PostEffectsBase {
public Shader motionBlurShader; //声明shader
private Material motionBlurMaterial = null;//声明材质
public Material material//创建材质
{
get
{
motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);//把shader给新建的材质
return motionBlurMaterial;//返回材质
}
}
//定义运动模糊在混合图像时使用的迷糊参数
[Range(0.0f, 0.9f)]//为了防止拖尾效果完全替代前帧的渲染结果,所以把值控制在0到0.9范围内
public float blurAmount = 0.5f;
private RenderTexture accumulationTexture;//定义一个RenderTexture类型的变量,保存之前图像叠加的结果
//在该脚本不运行时,即调用OnDiable时,立即销毁accumulationTexture,这是因为希望在下一次开始应用运动模糊时重新叠加图像
private void OnDisable()
{
DestroyImmediate(accumulationTexture);
}
//定义迷糊使用的OnRenderImage函数
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
//确认材质可用
if (material !=null)
{
//判断用于混合图像的accumationTexture是否为空,是否与当前分辨率不相等,
if (accumulationTexture ==null ||accumulationTexture .width !=src .width||accumulationTexture .height !=src .height )
{
//立即销毁
DestroyImmediate(accumulationTexture);
//创建一个符合大小的得变量
accumulationTexture = new RenderTexture(src.width, src.height, 0);
//HideAndDontSave:保留对象到新场景,与DontSave类似,但不会显示在Hierarchy面板中
accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
Graphics.Blit(src, accumulationTexture);//使用当前的帧图像初始化accumulationTexture
}
//恢复操作(restore operation):发生在渲染到纹理而该纹理又没有被提前清空或销毁的情况下
//调用函数 【accumulationTexture.MarkRestoreExpected】来表明需要进行一个渲染纹理的恢复操作
accumulationTexture.MarkRestoreExpected();
//每次调用OnRenderImage时都需要把当前的帧图像和accumulationTexture中的图像混合,accumulationTexture纹理不需要提前清空,因为她保存了我们之前的混合结果
material.SetFloat("_BlurAmount", 1.0f - blurAmount);//将参数传给材质
Graphics.Blit(src, accumulationTexture, material);//把当前屏幕图像src叠加到accumulationTexture中
Graphics.Blit(accumulationTexture, dest);//把结果显示到屏幕上
}else
{
Graphics.Blit(src, dest);
}
}
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//12.6运动模糊
Shader "Unlit/Chapter12-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;
}
//定义两个片元着色器,为了保护A通道不受到混合的影响
//渲染rgb通道
fixed4 fragRGB(v2f i) : SV_Target{
//对图像进行采样,把A通道的值设置为_BlurAmount,以便在后面混合时可以使用它的透明通道进行混合
return fixed4(tex2D(_MainTex,i.uv).rgb,_BlurAmount);
}
//渲染A通道
half4 fragA(v2f i) : SV_Target{
//直接返回采样结果
return tex2D(_MainTex,i.uv);
}
ENDCG
ZTest Always Cull Off ZWrite Off
//之所以把把A通道和RGB通道分开,是因为在更新RGB时我们需要设置它的A通道的来混合图像,但又不希望A通道的值写入渲染纹理中
//用来更新渲染RGB通道
Pass
{
//混合 A 1-A
Blend SrcAlpha OneMinusSrcAlpha
//颜色遮罩 RGB
ColorMask RGB
CGPROGRAM
#pragma vertex vert
#pragma fragment fragRGB
ENDCG
}
//用来更新渲染A通道
Pass {
//混合 1 0
Blend One Zero
//颜色遮罩 A
ColorMask A
CGPROGRAM
#pragma vertex vert
#pragma fragment fragA
ENDCG
}
}
Fallback Off
}
关于shader的Blend参考:https://blog.csdn.net/baicaishisan/article/details/79072127
关于C#的HideFlags类参考:https://blog.csdn.net/Dylan_Day/article/details/80350420
不得不说,当个小白也挺好,学习资源不愁找,哈哈~