运动模糊
一、什么是动态模糊
运动模糊效果
是一种 用于模拟真实世界中快速移动物体产生的模糊现象的图像处理技术
当一个物体以较高速度移动时,由于人眼或摄像机的曝光时间过长,该物体会在图像中留下模糊
的运动轨迹。这种效果游戏、动画、电影中被广泛应用,以增加视觉真实性和动感。
二、基本原理
- 累积缓存
物体快速运动时存储多帧图像信息,取他们之间的加权平均值作为最后的运动模糊的图像
- 速度缓存
物体快速运动时存储多帧运动速度信息,利用速度来决定模糊的方向和大小
- 优点:性能积累缓存好
- 缺点:效果较差,可能产生重影和伪影 ## 三、如何实现
这里我们通过基于累积缓存来实现动态模糊效果。
但是我们不需要像累积缓存中那样存储多张场景信息,但是需要保存之前的渲染结果,不断把当前的渲
染图像叠加到之前的渲染图像中,从而产生一种运动轨迹的视觉效果。
相当于是基于累积缓存的优化,
性能会更好,但是模糊效果可能略有欠缺,但是效果也是可以接受的。
简单来说:
因为使用Graphics.Blit(源纹理,目标纹理,材质)方法时
如果目标纹理中存在内容,会直接将目标纹理中的颜色作为颜色缓冲区的颜色
所以通过用一个RenderTexture记录上一次渲染的信息,然后每一次用新的屏幕图像信息和上一次的图像信息进
行混合渲染,从而产生模糊效果(相当于用一张图保留了之前n次的叠加渲染结果)
1.主要混合思路
- RGB通道由两张图片根据模糊程度决定最终效果
- A通道根据当前屏幕图像决定
大致流程图
Shader中的具体表现
- 利用一个模糊程度的变量控制运动的模糊程度
- 利用两个Pass进行混合处理的方式
- 第一个Pass
当前屏幕图像 和 上一次屏幕图像的 进行指定的RGB通道的颜色混合
目的是:利用模糊程度的参数控制两张图片的混合效果,值越大,上次屏幕保留的内容就越多
- 第二个Pass
利用第一个Pass处理好后的颜色在 和 源纹理进行 A的通道混合
目的是:保留源纹理的透明信息
2.如何设置混合方式
第一个Pass 1 2
| Blend SrcAlpha OneMinusSrcAlpha //((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))) ColorMask RGB //(只改变颜色缓冲区的RGB通道)
|
第二个Pass 1 2
| Blend One Zero //(最终颜色 = (源颜色 * 1) + (目标颜色 * 0)) ColorMask A //(只改变颜色缓冲区的A通道)
|
四、具体实现
1.实现思路
1.)Shader部分
- 属性声明
- 主纹理 _MainTex
- 模糊程度 _BlurAmount
- 共享CG代码部分
- 内置文件引用
- 属性映射
- 结构体(顶点坐标和uv)
- 顶点着色器(顶点坐标转换、uv坐标赋值)
- 屏幕后处理标配
- ZTest Always
- Cull Off
- ZWrite Off
- 第一个Pass(用于混合RGB通道)
- 混合因子和颜色蒙版设置
- 片元着色器
主要是对纹理采样后利用模糊程度作为A通道与颜色缓冲区颜色进行混合
- 第二个Pass(用于混合A通道)
- FallBack off
2.)C#部分
- 声明成员属性
- 公共的模糊程度
- 私有的堆积纹理 accumulation Texture 主要作用就是用于存储上次的渲染结果
- 重写OnRenderTexture
- 若堆积纹理为空或宽高变化 则初始化渲染纹理 设置其hideFlags为HideFlags.HideAndDontSave(让其不保存)
- 设置模糊层度属性
- 将源纹理利用材质写入到堆积纹理中(相当于记录本次渲染结果)
- 将堆积纹理写入目标纹理中
- 组件失活时,销毁堆积纹理
2.实现示例
1.)Shader部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| Shader "Unlit/MotionBlur" { Properties { _MainTex ("Texture", 2D) = "white" {} //模糊程度变量 _BlurAmount("BlurAmount" ,Float) = 0.5 } SubShader { CGINCLUDE #include "UnityCG.cginc"
sampler2D _MainTex; float _BlurAmount; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };
v2f vert(appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; } ENDCG Tags {"RenderType"="Opaque"} //第一个Pass 用于混合RGB通道 Pass { //屏幕后处理标配 ZTest Always Cull Off ZWrite Off Blend SrcAlpha OneMinusSrcAlpha ColorMask RGB CGPROGRAM #pragma vertex vert #pragma fragment fragRGB
fixed4 fragRGB(v2f i) : SV_Target { //这里相当于((源颜色 * _BlurAmount) + (目标颜色 * (1 - _BlurAmount))) return fixed4(tex2D(_MainTex,i.uv).rgb, _BlurAmount); } ENDCG } //第二个Pass 用于混合A通道 Pass { //(最终颜色 = (源颜色 * 1) + (目标颜色 * 0)) Blend One Zero ColorMask A CGPROGRAM #pragma vertex vert #pragma fragment fragA
fixed4 fragA(v2f i) : SV_Target { return fixed4(tex2D(_MainTex,i.uv)); } ENDCG } } Fallback Off }
|
2.)C#部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class MotionBlur : PostEffectBase { [Range(0,0.9f)] public float blurAmount = 0.5f; private RenderTexture _accumulationTexture; protected override void OnRenderImage(RenderTexture source, RenderTexture destination) { if (material) { if (_accumulationTexture == null || _accumulationTexture.width != source.width || _accumulationTexture.height != source.height ) { DestroyImmediate(_accumulationTexture); _accumulationTexture = new RenderTexture(source.width, source.height, 0); _accumulationTexture.hideFlags = HideFlags.HideAndDontSave; Graphics.Blit(source,_accumulationTexture); } material.SetFloat("_BlurAmount",1.0f - blurAmount); Graphics.Blit(source, _accumulationTexture, material); Graphics.Blit(_accumulationTexture,destination); } else Graphics.Blit(source, destination); } private void OnDisable() { DestroyImmediate(_accumulationTexture); } }
|