Unity Shader学习:动态模糊

动态模糊一般有帧混合和motion vector两种,这里主要介绍motion vector的方法。
Keijiro源码:https://github.com/keijiro/KinoMotion
当物体快速旋转或者运动时:

有曝光时间的效果


关闭动态模糊,虽然物体在高速旋转,没有曝光时间

c#部分:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;[ImageEffectAllowedInSceneView]
public class ZzcMotionBlur : MonoBehaviour {public Material _material;[Range(0,100)]/// <summary>/// 动态模糊最大长度(值为屏幕高度的百分比)/// </summary>public float kMaxBlurRadius = 5f;[Range(1,360)]/// <summary>/// 曝光时间/// </summary>public float shutterAngle = 270f;[Range(1,32)]/// <summary>/// 采样数/// </summary>public int sampleCount = 8;// 速度图格式RenderTextureFormat _vectorRTFormat = RenderTextureFormat.RGHalf;// 速度、深度图格式RenderTextureFormat _packedRTFormat = RenderTextureFormat.ARGB2101010;void Update () {if (shutterAngle > 0)GetComponent<Camera>().depthTextureMode |=DepthTextureMode.Depth | DepthTextureMode.MotionVectors;}//新建rtRenderTexture GetTemporaryRT(Texture source, int divider, RenderTextureFormat format){var w = source.width / divider;var h = source.height / divider;var linear = RenderTextureReadWrite.Linear;var rt = RenderTexture.GetTemporary(w, h, 0, format, linear);rt.filterMode = FilterMode.Point;return rt;}void ReleaseTemporaryRT(RenderTexture rt){RenderTexture.ReleaseTemporary(rt);}private void OnRenderImage(RenderTexture source, RenderTexture destination){if (!_material||shutterAngle == 0){return;}// 最大模糊像素(屏幕百分比)var maxBlurPixels = (int)(kMaxBlurRadius * source.height * 0.01f);// 根据最大模糊像素降分辨率// 需要为8的倍数并大于maxBlurPixelsvar tileSize = ((maxBlurPixels - 1) / 8 + 1) * 8;// 1st pass - 获取像素速度和深度// 快门开角跟曝光速度的关系如下:// 开角 / 360度 = 曝光时间 / 画幅停顿时间------------(像素速度缩放)var velocityScale = shutterAngle / 360;_material.SetFloat("_VelocityScale", velocityScale);_material.SetFloat("_MaxBlurRadius", maxBlurPixels);_material.SetFloat("_RcpMaxBlurRadius", 1.0f / maxBlurPixels);var vbuffer = GetTemporaryRT(source, 1, _packedRTFormat);Graphics.Blit(null, vbuffer, _material, 0);// 2nd pass - 1/2 获取周围像素最大速度滤镜var tile2 = GetTemporaryRT(source, 2, _vectorRTFormat);Graphics.Blit(vbuffer, tile2, _material, 1);// 3rd pass - 1/4 获取周围像素最大速度滤镜var tile4 = GetTemporaryRT(source, 4, _vectorRTFormat);Graphics.Blit(tile2, tile4, _material, 2);ReleaseTemporaryRT(tile2);// 4th pass - 1/8 获取周围像素最大速度滤镜var tile8 = GetTemporaryRT(source, 8, _vectorRTFormat);Graphics.Blit(tile4, tile8, _material, 2);ReleaseTemporaryRT(tile4);// 5th pass - 根据模糊程度获取四周像素最大速度// 模糊百分比越大,采样偏移越大,获取的最大速度像素距离越远var tileMaxOffs = Vector2.one * (tileSize / 8.0f - 1) * -0.5f;_material.SetVector("_TileMaxOffs", tileMaxOffs);_material.SetInt("_TileMaxLoop", tileSize / 8);var tile = GetTemporaryRT(source, tileSize, _vectorRTFormat);Graphics.Blit(tile8, tile, _material, 3);ReleaseTemporaryRT(tile8);// 6th pass - 周围8像素最大速度var neighborMax = GetTemporaryRT(source, tileSize, _vectorRTFormat);Graphics.Blit(tile, neighborMax, _material, 4);ReleaseTemporaryRT(tile);// 7th pass - 合并_material.SetFloat("_LoopCount",sampleCount);_material.SetTexture("_NeighborMaxTex", neighborMax);_material.SetTexture("_VelocityTex", vbuffer);Graphics.Blit(source, destination, _material, 5);// 释放ReleaseTemporaryRT(vbuffer);ReleaseTemporaryRT(neighborMax);}
}

shader部分:

Shader "Zzc/ZzcMotionBlur"
{Properties{_MainTex ("Texture", 2D) = "white" {}}CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;float4 _MainTex_TexelSize;sampler2D _CameraDepthTexture;float4 _CameraDepthTexture_TexelSize;sampler2D _CameraMotionVectorsTexture;float4 _CameraMotionVectorsTexture_TexelSize;sampler2D _VelocityTex;float4 _VelocityTex_TexelSize;sampler2D _NeighborMaxTex;float4 _NeighborMaxTex_TexelSize;float _VelocityScale;int _TileMaxLoop;float2 _TileMaxOffs;half _MaxBlurRadius;float _RcpMaxBlurRadius;half _LoopCount;// 计算线性深度float LinearizeDepth(float z){//x是正交的相机的宽度,y是正交的相机的高度,z是未使用的,为正交的相机时w为1.0,透视相机时w为0.0float isOrtho = unity_OrthoParams.w;float isPers = 1 - unity_OrthoParams.w;//用于线性Z缓冲器值。x是(1-far/near),y是(far/near),z为(x/far)和w是(y/far)。z *= _ZBufferParams.x;return (1 - isOrtho * z) / (isPers * z + _ZBufferParams.y);}//返回最长向量half2 VMax(half2 v1, half2 v2){return dot(v1, v1) < dot(v2, v2) ? v2 : v1;}// Fragment shader: 得到速度图、深度图half4 frag_VelocitySetup(v2f_img i) : SV_Target{//获得像素相对上一帧位移向量float2 v = tex2D(_CameraMotionVectorsTexture, i.uv).rg;// 将曝光时间和向量转到像素单位长度// zw为屏幕宽和高,0~1长度的向量转到像素单位长度v *= (_VelocityScale * 0.5) * _CameraMotionVectorsTexture_TexelSize.zw;// 用最大模糊半径限制向量长度v /= max(1, length(v) * _RcpMaxBlurRadius);//获取像素深度half d = LinearizeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));// 保存在 10/10/10/2 格式return half4((v * _RcpMaxBlurRadius + 1) / 2, d, 0);}// Fragment shader: 获取四周最大速度像素并转换至(-1,1)---2像素half4 frag_TileMax1(v2f_img i) : SV_Target{float4 d = _MainTex_TexelSize.xyxy * float4(-0.5, -0.5, 0.5, 0.5);half2 v1 = tex2D(_MainTex, i.uv + d.xy).rg;half2 v2 = tex2D(_MainTex, i.uv + d.zy).rg;half2 v3 = tex2D(_MainTex, i.uv + d.xw).rg;half2 v4 = tex2D(_MainTex, i.uv + d.zw).rg;//0~1转-1~1向量v1 = (v1 * 2 - 1) * _MaxBlurRadius;v2 = (v2 * 2 - 1) * _MaxBlurRadius;v3 = (v3 * 2 - 1) * _MaxBlurRadius;v4 = (v4 * 2 - 1) * _MaxBlurRadius;return half4(VMax(VMax(VMax(v1, v2), v3), v4), 0, 0);}// Fragment shader: 获取四周最大速度像素---2像素half4 frag_TileMax2(v2f_img i) : SV_Target{float4 d = _MainTex_TexelSize.xyxy * float4(-0.5, -0.5, 0.5, 0.5);half2 v1 = tex2D(_MainTex, i.uv + d.xy).rg;half2 v2 = tex2D(_MainTex, i.uv + d.zy).rg;half2 v3 = tex2D(_MainTex, i.uv + d.xw).rg;half2 v4 = tex2D(_MainTex, i.uv + d.zw).rg;return half4(VMax(VMax(VMax(v1, v2), v3), v4), 0, 0);}// Fragment shader: 根据模糊程度获取四周最大速度像素half4 frag_TileMaxV(v2f_img i) : SV_Target{float2 uv0 = i.uv + _MainTex_TexelSize.xy * _TileMaxOffs.xy;float2 du = float2(_MainTex_TexelSize.x, 0);float2 dv = float2(0, _MainTex_TexelSize.y);half2 vo = 0;UNITY_LOOP for (int ix = 0; ix < _TileMaxLoop; ix++){UNITY_LOOP for (int iy = 0; iy < _TileMaxLoop; iy++){float2 uv = uv0 + du * ix + dv * iy;vo = VMax(vo, tex2D(_MainTex, uv).rg);}}return half4(vo, 0, 0);}// Fragment shader: 获取四周8像素最大速度half4 frag_NeighborMax(v2f_img i) : SV_Target{//中心点偏移const half cw = 1.01f;float4 d = _MainTex_TexelSize.xyxy * float4(1, 1, -1, 0);half2 v1 = tex2D(_MainTex, i.uv - d.xy).rg;half2 v2 = tex2D(_MainTex, i.uv - d.wy).rg;half2 v3 = tex2D(_MainTex, i.uv - d.zy).rg;half2 v4 = tex2D(_MainTex, i.uv - d.xw).rg;half2 v5 = tex2D(_MainTex, i.uv).rg * cw;half2 v6 = tex2D(_MainTex, i.uv + d.xw).rg;half2 v7 = tex2D(_MainTex, i.uv + d.zy).rg;half2 v8 = tex2D(_MainTex, i.uv + d.wy).rg;half2 v9 = tex2D(_MainTex, i.uv + d.xy).rg;half2 va = VMax(v1, VMax(v2, v3));half2 vb = VMax(v4, VMax(v5, v6));half2 vc = VMax(v7, VMax(v8, v9));return half4(VMax(va, VMax(vb, vc)) / cw, 0, 0);}// 判断x/y小数部分是否大于0.5bool Interval(half phase, half interval){return frac(phase / interval) > 0.499;}// 交错梯度噪声 from Jimenez 2014 http://goo.gl/eomGso// 可在shadertoy预览效果float GradientNoise(float2 uv){uv = floor((uv + _Time.y) * _ScreenParams.xy);float f = dot(float2(0.06711056f, 0.00583715f), uv);return frac(52.9829189f * frac(f));}// 抖动float2 JitterTile(float2 uv){float rx, ry;sincos(GradientNoise(uv + float2(2, 0)) * UNITY_PI * 2, ry, rx);return float2(rx, ry) * _NeighborMaxTex_TexelSize.xy / 4;}// 采样速度图,得到模糊像素的速度half3 SampleVelocity(float2 uv){half3 v = tex2Dlod(_VelocityTex, float4(uv, 0, 0)).xyz;return half3((v.xy * 2 - 1) * _MaxBlurRadius, v.z);}// Vertex shader 合并用struct v2f_multitex{float4 pos : SV_POSITION;float2 uv0 : TEXCOORD0;float2 uv1 : TEXCOORD1;};v2f_multitex vert_Multitex(appdata_full v){v2f_multitex o;o.pos = UnityObjectToClipPos(v.vertex);o.uv0 = v.texcoord.xy;o.uv1 = v.texcoord.xy;
#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0.0)o.uv1.y = 1.0 - v.texcoord.y;
#endifreturn o;}// fragment shader 合并half4 frag_Reconstruction(v2f_multitex i) : SV_Target{// 背景色const half4 c_p = tex2D(_MainTex, i.uv0);// 采样速度图(合并、模糊都要用,所以先做成一张图)const half3 vd_p = SampleVelocity(i.uv1);// 速度长度const half l_v_p = max(length(vd_p.xy), 0.5);// 周围最大速度的抖动采样const half2 v_max = tex2D(_NeighborMaxTex, i.uv1 + JitterTile(i.uv1)).xy;// 速度长度const half l_v_max = length(v_max);// (四周)模糊后最大速度小于2个像素直接返回原颜色if (l_v_max < 2) return c_p;// Use V_p as a secondary sampling direction except when it's too small// compared to V_max. This vector is rescaled to be the length of V_max.// 中心像素点的速度如果没有周围最大一半长,为周围最大速度,否则乘以最大长度和中心长度比值//const half2 v_alt = (l_v_p * 2 > l_v_max) ? vd_p.xy * (l_v_max / l_v_p) : v_max;// 采样数,像素距离/2或者自定义中最小的值const half sc = floor(min(_LoopCount, l_v_max / 2));// 循环变量(从最边缘采样开始)const half dt = 1 / sc;// 越边缘偏移越小const half t_offs = (GradientNoise(i.uv0) - 0.5) * dt;half t = 1 - dt / 2;half count = 0;// 模板速度,最小为1half l_v_bg = max(l_v_p, 1);// 颜色half4 acc = 0;UNITY_LOOP while (t > dt / 4){// Sampling direction (switched per every two samples)// 每两个循环切换一次速度长度//const half2 v_s = Interval(count, 4) ? v_alt : v_max;//const half2 v_s = v_max;// Sample position (inverted per every sample)// 每个循环切换一次偏移位置的方向const half t_s = (Interval(count, 2) ? -t : t) + t_offs;// 速度长度*偏移大、方向=偏移距离const half l_t = l_v_max * abs(t_s);// uv的偏移const float2 uv0 = i.uv0 + v_max * t_s * _MainTex_TexelSize.xy;const float2 uv1 = i.uv1 + v_max * t_s * _VelocityTex_TexelSize.xy;// 循环时原颜色const half3 c = tex2Dlod(_MainTex, float4(uv0, 0, 0)).rgb;// 循环时的速度、深度const half3 vd = SampleVelocity(uv1);// 和上一帧此像素点的深度差// 可以判断是否处在边缘附近const half fg = saturate((vd_p.z - vd.z) * 20/vd_p.z);// 根据深度差插值速度混合速度// length(vd.xy)上一帧像素移动距离// l_v_bg模板速度,这一帧像素移动距离const half l_v = lerp(l_v_bg, length(vd.xy), fg);// 权重,过滤没有运动的像素//const half w = saturate(l_v - l_t)/l_v * (1 - t);const half w = saturate(l_v - l_t) * (1 - t);              // 按权累加acc += half4(c, 1) * w;// 更新模板最大速度l_v_bg = max(l_v_bg, l_v);// 每两帧减少权重(对应位置的正负变化)t = Interval(count, 2) ? t - dt : t;count += 1;}// 加回原颜色// 采样数越多,速度越快,原颜色越暗//acc += half4(c_p.rgb, 1) * (1.2 / (l_v_bg * sc * 2));acc += half4(c_p.rgb, 1)*0.001;return half4(acc.rgb / acc.a, c_p.a);}ENDCGSubshader{// Pass 0: Velocity texture setupPass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag_VelocitySetup#pragma target 3.0ENDCG}// Pass 1: TileMax filter (2 pixels width with normalization)Pass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag_TileMax1#pragma target 3.0ENDCG}// Pass 2: TileMax filter (2 pixels width)Pass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag_TileMax2#pragma target 3.0ENDCG}// Pass 3: TileMax filter (variable width)Pass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag_TileMaxV#pragma target 3.0ENDCG}// Pass 4: NeighborMax filterPass{CGPROGRAM#pragma vertex vert_img#pragma fragment frag_NeighborMax#pragma target 3.0ENDCG}// Pass 5: Reconstruction filterPass{CGPROGRAM#pragma vertex vert_Multitex#pragma fragment frag_Reconstruction#pragma target 3.0ENDCG}}
}

Unity Shader学习:动态模糊(shutter angle方式)相关推荐

  1. Unity Shader学习:动态雾

    Unity Shader学习:动态雾 先将相机近裁面四个角向量传给shader,再通过观察空间下的深度值和相机位置算出像素在世界坐标系的位置,通过世界空间高度值来设定雾的范围和浓度,然后通过噪声和uv ...

  2. Unity Shader 学习笔记(33) 全局光照(GI)、反射探针、线性空间和伽马空间、高动态范围(HDR)

    Unity Shader 学习笔记(33) 全局光照(GI).反射探针.线性空间和伽马空间.高动态范围(HDR) 参考书籍:<Unity Shader 入门精要> [<Real-Ti ...

  3. Unity Shader学习-高光反射

    Unity Shader学习-高光反射 高光反射计算公式 高光反射 = 光源的色彩和强度 * 材质的高光反射系数 * pow(max(0,视角方向 · 反射方向),_Gloss) 视角方向 = ref ...

  4. Unity Shader学习:体积光/体积阴影

    Unity Shader学习:体积光/体积阴影 在前向渲染下实现平行光的体积光影效果,需要全屏深度图,延迟渲染会更划算. 思路:通过ray marching的步进点位置计算该点是否在阴影中,采样阴影贴 ...

  5. Unity Shader学习:SSR屏幕空间反射

    Unity Shader学习:SSR屏幕空间反射 本文在前向渲染模式下实现,延迟渲染更适合SSR,这里只简单的实现下,未作更深入的优化. 思路:沿视线和法线的反射向量步进光线,判断打到物体(这里用的是 ...

  6. 《Unity Shader入门精要》学习笔记第5章 开始Unity Shader学习之旅

    本文章用于帮助自己学习,因此只记录一些个人认为比较重要或者还不够熟悉的内容. 原作者:http://blog.csdn.net/candycat1992/article/ 第五章 开始Unity Sh ...

  7. Unity Shader学习:CPU/GPU boid

    Unity Shader学习:CPU/GPU boid 参考:https://github.com/chenjd/Unity-Boids-Behavior-on-GPGPU https://www.y ...

  8. Unity Shader学习:SSAO屏幕环境光遮蔽

    Unity Shader学习:SSAO屏幕环境光遮蔽 主要思路:1.随机采样像素法线半球周围的像素,平均对比与该像素深度是否处在暗处.2.双边滤波去噪点.3.后期AO图与原图混合. 原文链接:http ...

  9. Unity Shader学习:水墨效果

    Unity Shader学习:水墨效果 偶然在网上看到9级铁甲蛹大神的水墨风格后处理觉得挺有意思,参照着实现一下,还是涉及到之前油画效果的算法,叫什么滤波暂时不清楚,应该用来处理手绘效果挺多的. 水墨 ...

最新文章

  1. 压缩aspx页面,移除aspx多余的空格 供学习参考
  2. hdu 5100 n*n棋盘放k*1长方条最多覆盖面积
  3. 支付宝的一些小问题,注意事项等等,等用得时候在来写写
  4. 【Java】辨析jvm.dll、java.exe、javaw.exe、javaws.exe
  5. 大一计算机在线考试,大一计算机考试题(含答案).pdf
  6. [深度学习TF2][RNN-LSTM]文本情感分析包含(数据预处理-训练-预测)
  7. 跨境电商ERP哪个好?
  8. C# : 操作Word文件的API - (将C# source中的xml注释转换成word文档)
  9. 卷积神经网络---文本分类原理及代码
  10. 超像素分割算法研究:SLIC分割算法原理讲解
  11. linux每个phy一个接口,mdio
  12. API 接口加密及请求参数加密
  13. 华为交换机配置IPSG防止DHCP动态主机私自更改IP地址
  14. vue中使用腾讯视频播放器
  15. 无法支持计算机上的硬件,win7“不支持的硬件,你的电脑使用的处理器专为最新版win...
  16. 虚幻引擎4(UE4)的基本操作Actor的操作
  17. 一些常用CSS样式整理
  18. 普通运维人员真的就是秋后的蚂蚱吗?
  19. 【Vue作业]---Vue登录注册界面
  20. 为了结婚领证,我做了个「一键结婚」插件

热门文章

  1. 音乐及游戏爱好者的福利,小鹏P7上新网易云及阴阳师,赶快看看吧
  2. 【疯壳·无人机开发教程1】开源编队无人机-开机测试
  3. 【TCP wrappers】关于/etc/hosts.allow /etc/hosts.deny
  4. 远程服务器拷贝数据库或者大量数据,出现会话空闲时间已超出限制,将在2分钟之内断开连接
  5. Nvidia AGX Xavier MAX9286 GMSL 载板
  6. CWRU数据集-美国西储大学轴承数据
  7. 【For非数学专业】通俗理解似然函数、概率、极大似然估计和对数似然
  8. uvalive 6528(DAG,递推,想法/bitset, 好题)
  9. pygame安装(2020版超详细)
  10. 三维向量类Vector类封装,包含三维向量一些基本运算