UnityShader26:运动模糊
一、在 Unity 中使用深度纹理或法线纹理
前置:OpenGL基础29:深度测试,关于深度测试的流程以及深度值的算法、空间变换都在这里提到过
在 Unity 中,想要在着色器中获得当前摄像机的深度纹理或者法线纹理,只需要设置 Camera 组件的 depthTextureMode
//让摄像机产出一张深度纹理
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
//让摄像机产出一张深度-法线纹理
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals
深度纹理来自于深度缓存,若是无法直接获取深度缓存,Unity 就会选择渲染类型为 Opaque、渲染队列 < 2500 的物体,通过着色器替换法渲染它们到深度和法线纹理中,其中选用的 Pass 为 ShadowCaster
着色器获得深度纹理的方法如下:
sampler2D _CameraDepthTexture;
//……
fixed4 frag(vert2frag i): SV_Target
{//通过深度纹理和纹理坐标获取深度值,考虑了平台差异,内部实现在HLSLSupport.cginc中float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.zw);d = Linear01Depth(d); //LinearEyeDepth(d) / Far//d = LinearEyeDepth(d); //将深度值转回正常视角空间下,范围为[Near, Far]return float4(d, d, d, 0.0);
}
![](/assets/blank.gif)
同理,深度-法线纹理通过对 _CameraDepthNormalsTexture 进行采样获取深度和法线信息,可以使用 DecodeDepthNormal 函数对其进行解码,其内部实现如下:
inline void DecodeDepthNormal(float4 enc, out float depth, out float3 normal)
{depth = DecodeFloatRG(enc.zw);normal = DecodeViewNormalStereo(enc);
}
其得到的深度值是线性的,得到的法线是视角空间下的法线方向
二、运动模糊原理
本质上是通过在片段着色器中,得到顶点在这一帧及上一帧在屏幕上的位置,然后两点插值混合后得到当前片段的最终颜色,这样相对于之前的模糊采样,其模糊的方向就能和物体的实际运动方向相契合
实现运动模糊需要拿到每个片段的深度值,然后和屏幕 xy 坐标组成 NDC 空间下的坐标
转为具体步骤:
- 获得当前帧摄像机 VP 矩阵的逆矩阵 IVP
- 获得上一帧摄像机 VP 矩阵
- 每帧将 ①② 传入着色器
- 在片段着色器中,通过屏幕坐标和深度值拼出当前的 NDC 空间坐标
- 拿到 NDC 坐标后通过 IVP 逆回世界空间坐标
- 再拿这个世界空间坐标乘上得到的上一帧摄像机 VP 矩阵,获得上一帧的 NDC 空间坐标
- 好了到这里上一帧的 NDC 空间坐标、当前帧的 NDC 空间坐标都有了,就可以轻松得到每一个每个像素点上一帧挪动到这一帧的位置向量(Motion Vector)
- 根据这个向量计算运动模糊
对于第④步的坑:NDC 空间 xyz 的坐标范围是 [-1, 1],这是正常通过投影矩阵变换得到的结果,但是 Unity 本身处理了原生的 P 矩阵,这样 z 轴的范围就是 [0, 1] 而不是 [-1, 1],因此是否在计算 NDC 坐标时对深度值 z 进行映射有待商榷
对于第⑤步的问题:要知道根据 P 矩阵算出的结果,它的第四维 w(齐次坐标)往往不为1,是后台帮我们做了 /w(齐次剪裁)的操作,当然在根据屏幕坐标和深度值拼出的 NDC 坐标也是剪裁操作之后的,可惜的是这时并不能知道齐次坐标 w,所以需要在逆回世界空间后,再将世界空间坐标除以它的 w 那一维,这样的到的才是真正的世界空间坐标
三、代码示例
脚本部分:没有什么特殊之处
- Camera.depthTextureMode:~
- Camera.projectionMatrix:摄像机当前 P 矩阵
- Camera.worldToCameraMatrix:摄像机当前 V 矩阵
using UnityEngine;
using System.Collections;public class MotionBlur: PostEffectsBase
{public Shader shader;private Material _material;public Material material{get{_material = CheckShaderAndCreateMaterial(shader, _material);return _material;}}private Camera _myCamera;public Camera myCamera{get{if (_myCamera == null)_myCamera = GetComponent<Camera>();return _myCamera;}}//采样间隔(单位像素比例)[Range(0.0f, 1.0f)]public float blurSize = 0.5f;//采样数[Range(1, 100)]public int blurNum = 20;private Matrix4x4 previousVPMatrix;void OnEnable(){//通知摄像机需要深度纹理,此后可以在着色器中使用//https://docs.unity3d.com/2021.1/Documentation/ScriptReference/DepthTextureMode.htmlmyCamera.depthTextureMode |= DepthTextureMode.Depth;//上一帧的摄像机 VP 矩阵previousVPMatrix = myCamera.projectionMatrix * myCamera.worldToCameraMatrix;}void OnRenderImage(RenderTexture src, RenderTexture dest){if (material != null){material.SetFloat("_BlurSize", blurSize);material.SetInt("_BlurNum", blurNum);material.SetMatrix("_PreviousVPMatrix", previousVPMatrix);//当前帧摄像机 VP 矩阵Matrix4x4 currentVPMatrix = myCamera.projectionMatrix * myCamera.worldToCameraMatrix;//矩阵求逆,得到 VP 矩阵的逆矩阵 IVPMatrix4x4 currentIVPMatrix = currentVPMatrix.inverse;material.SetMatrix("_CurrentIVPMatrix", currentIVPMatrix);previousVPMatrix = currentVPMatrix;Graphics.Blit(src, dest, material);}else{Graphics.Blit(src, dest);}}
}
片段着色器部分:
一个可能的疑问:为什么当摄像机移动的时候,会出现运动模糊现象,而当且仅当摄像机视角变化时,却不会出现运动模糊现象?
→ 原因:其实仅当摄像机视角变化时必然也会出现运动模糊现象,虽然相对于上一帧 P 矩阵没有变,但是 V 矩阵变了。而之所以看上去没有出现运动模糊现象,只是因为效果非常不明显(Motion Vector 长度过小),这在高速调整视角时可以被观察而证实
在重新构建NDC空间坐标时,一个非常坑的点:网上很多的博客包括《UnityShader入门精要》这本书在内,在构建 NDC 坐标时都对采样得到的深度 d 进行了 [0, 1] 到 [-1, 1] 的映射,但对于 DirectX11、DirectX12、PS4、XboxOne 和 Metal,现在使用的都是新的方法深度反转(Reversed direction),即NDC的z轴取值范围是[1, 0](其他的图形接口,保持传统的取值范围:OpenGL是[-1, 1],DirectX是[0, 1])。当然 Unity 也考虑了 Reversed direction,深度缓冲也是同样的道理,因此本例中 NDC 的 z 轴范围正是[1, 0]。综上所述:这里不要对采样得到的深度值d进行映射!!它正好就是NDC下的 z
Shader "Jaihk662/MotionBlur"
{Properties{_MainTex("Base (RGB)", 2D) = "white" {}_BlurSize("Blur Size", Float) = 1.0_BlurNum("Blur Num", int) = 20}CGINCLUDE#include "UnityCG.cginc"int _BlurNum;sampler2D _MainTex;half _BlurSize;//如果设置了 myCamera.depthTextureMode |= DepthTextureMode.Depth,那么这里就可以通过 _CameraDepthTexture 拿到深度图sampler2D _CameraDepthTexture;half4 _MainTex_TexelSize;float4x4 _CurrentIVPMatrix;float4x4 _PreviousVPMatrix;struct vert2frag{float4 pos: SV_POSITION;half4 uv: TEXCOORD0;};vert2frag vert(appdata_img v){vert2frag 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;#endifreturn o;}fixed4 frag(vert2frag i): SV_Target{//通过深度纹理和纹理坐标获取深度值,考虑了平台差异,内部实现在HLSLSupport.cginc中float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.zw);//重新构建NDC空间坐标,其中xy都从[0,1]范围映射到[-1,1]就好//对于DirectX11、DirectX12、PS4、XboxOne和Metal,现在使用的都是新的方法深度反转(Reversed direction),即NDC的z轴取值范围是[1, 0](其他的图形接口,保持传统的取值范围:OpenGL是[-1,1],DirectX是[0,1])//同理:Unity也考虑了Reversed direction,深度缓冲也是同样的道理,因此本例中NDC的z轴范围是[1, 0]。综上所述:这里不要对采样得到的深度值d进行映射!!它正好就是NDC下的z(网上的大多数博客,包括是《UnityShader入门精要》这本书,对于新版本的Unity这里就都已经不对了)//https://docs.unity3d.com/Manual/SL-PlatformDifferences.htmlfloat4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d, 1);//从NDC空间逆回世界空间,当然这个IVP矩阵是前一帧的IVP矩阵float4 D = mul(_CurrentIVPMatrix, H);//除以w分量拿到真正的世界空间坐标float4 worldPos = D / D.w;//再转回来,不过这次的VP矩阵是当前帧的VP矩阵float4 currentPos = H;float4 previousPos = mul(_PreviousVPMatrix, worldPos);previousPos /= previousPos.w;//得到相邻两帧屏幕坐标的差,再乘上采样间隔//这个地方为什么要除以2:因为currentPos和previousPos都是NDC空间坐标,所以在计算时需要将它们转回屏幕空,而化简后的结果就是相差除以2float2 velocity = (currentPos.xy - previousPos.xy) / 2 * _BlurSize;//在这两帧NDC空间坐标中间进行采样float2 uv = i.uv.xy;float4 color = float4(0.0, 0.0, 0.0, 0.0);for (int it = 0; it < _BlurNum; it++){float4 currentColor = tex2D(_MainTex, uv);color += currentColor;uv += velocity / _BlurNum;}color /= _BlurNum;//d = Linear01Depth(d);return fixed4(color.rgb, 1.0);}ENDCGSubshader{ZTest Always Cull Off ZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG}}FallBack Off
}
参考资料:
- https://zhuanlan.zhihu.com/p/64746514
- 《UnityShader入门精要》
- https://docs.unity3d.com/cn/current/Manual/SL-PlatformDifferences.html
- https://zhuanlan.zhihu.com/p/66175070
UnityShader26:运动模糊相关推荐
- Matlab图像复原(运动模糊、散焦模糊)
图像退化 图像退化的因素各种各样,但最主要的就是在得到图像,传送过程和保存的时候导致的,还有由于形成图像系统的不同.想要拍的物体和相机之间的相对运动,一些空气媒介等等都会让图像变得模糊不清楚.混入噪声 ...
- 如何消除摄影中的运动模糊?
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|计算机视觉life 如果你试过去拍摄一些运动场景,例如拍 ...
- 使用后期处理效果实现运动模糊
1.介绍 在电子游戏中模拟速度的一种最好的方法就是使用运动模糊.运动模糊是游戏中最重要的效果之一,尤其是在赛车游戏中,因为它可以增加真实感和速度感.运动模糊还可以帮助游戏画面的平滑,尤其是对于帧速小于 ...
- matlab 维纳滤波恢复运动模糊,运动模糊恢复专题
相关背景知识 1.运动模糊的定义 wiki百科上的定义是:运动模糊或运动模糊(motion blur)是静态场景或一系列的图片像电影或是动画中一样快速移动,使物体产生明显运动痕迹. [图片上传失败.. ...
- 【youcans 的 OpenCV 例程 200 篇】104. 运动模糊退化模型
欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中 欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中 [youcans 的 OpenCV 例程 2 ...
- Win8 Metro(C#)数字图像处理--2.50图像运动模糊
原文:Win8 Metro(C#)数字图像处理--2.50图像运动模糊 [函数名称] 图像运动模糊算法 MotionblurProcess(WriteableBitmap src,int ...
- ae运动模糊怎么调整_如何快速成长为一名AE高手?
Adobe After Effects是一种数字视觉效果.动态图形和合成应用程序,用于电影制作.视频游戏和电视制作的后期制作过程.最常见的操作便是AE用于抠像,跟踪,合成和动画. 图片来自:apple ...
- python实现运动模糊图像_OpenCV+Python实现图像运动模糊和高斯模糊
原标题:OpenCV+Python实现图像运动模糊和高斯模糊 运动模糊:由于相机和物体之间的相对运动造成的模糊,又称为动态模糊 OpenCV+Python实现运动模糊,主要用到的函数是cv2.filt ...
- 两个一样的图像相除会怎么样_【壮凌自动化分析】一种动力电池生产中基于图像运动模糊的速度检测方法...
一种动力电池生产中基于图像运动模糊的速度检测方法 1.西南大学 电子信息工程学院,重庆 400715) 2.非线性电路与智能信息处理重庆市重点实验室,重庆 400715) 1.当前背景与成熟方法介绍 ...
最新文章
- httpTomcat
- 【设计模式】-写在前面
- 积跬步,聚小流------html知识大纲归纳总结
- 键盘鼠标录制哪个好用_好看好用还不贵的那种键盘鼠标真的有吗?这次还真让我碰到了...
- mysql备份一个表到ftp_备份部分mysql表并上传至指定ftp服务器目录中
- 遥控器按键不灵的修复方法
- python博客开发教程_Django 博客开发教程 12 - 评论
- 前端学习(2328):angular之模板
- npm 编译打包vue_从零到一教你基于vue开发一个组件库
- leetcode880.DecodedStringatIndex
- linux下哪个输入法最好,[最好]linux下输入法→linux下输入法
- 2021-CVPR-Inpainting论文导读
- java实现生成pdf_Java 生成 PDF 文档
- 不带www的域名强制跳转到www域名,Nginx服务器rewrite重写
- 哔哩哔哩 机器人历险记_机器人历险记谁演的,机器人历险记的扮演者罗德尼资料介绍-易看TV...
- JAVA 短链码生成工具类
- 5个文献免费下载神器
- 【无线串口模块应用实例】防小人不防君子的智慧安防监测系统
- win10电脑发现不了华为share_huaweishare打开 电脑找不到手机
- 从张鑫旭的demo中,我学到了图像拉伸的原理
热门文章
- 零基础学python难吗-学习python12小时后,告诉你,学python真没你想的那么难!
- python怎么读取csv文件-python怎么读取csv文件
- 爬虫python能做什么-Python除了爬虫,还能干啥?
- python教程视频下载-Python学习精品教程,视频书籍打包下载
- 带网格的_【我看身边的网格化】申港街道:一人一板穿梭楼宇小巷 一网一格解决百姓问题...
- JavaScript错误与异常
- 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)...
- java 线性的排序算法_数据结构之排序算法Java实现(9)—— 线性排序之 基数排序算法...
- java.lang.ClassNotFoundException: org.openxmlformats.schemas.wordprocessingml.x2006.main.impl.CTPIm
- Docker镜像源更改