一、在 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);
}

着色器得到的深度纹理,如果全黑或全白,就需要调整下摄像机的远裁平面,不能太远

同理,深度-法线纹理通过对 _CameraDepthNormalsTexture 进行采样获取深度和法线信息,可以使用 DecodeDepthNormal 函数对其进行解码,其内部实现如下:

inline void DecodeDepthNormal(float4 enc, out float depth, out float3 normal)
{depth = DecodeFloatRG(enc.zw);normal = DecodeViewNormalStereo(enc);
}

其得到的深度值是线性的,得到的法线是视角空间下的法线方向

二、运动模糊原理

本质上是通过在片段着色器中,得到顶点在这一帧及上一帧在屏幕上的位置,然后两点插值混合后得到当前片段的最终颜色,这样相对于之前的模糊采样,其模糊的方向就能和物体的实际运动方向相契合

实现运动模糊需要拿到每个片段的深度值,然后和屏幕 xy 坐标组成 NDC 空间下的坐标

转为具体步骤:

  1. 获得当前帧摄像机 VP 矩阵的逆矩阵 IVP
  2. 获得上一帧摄像机 VP 矩阵
  3. 每帧将 ①② 传入着色器
  4. 在片段着色器中,通过屏幕坐标和深度值拼出当前的 NDC 空间坐标
  5. 拿到 NDC 坐标后通过 IVP 逆回世界空间坐标
  6. 再拿这个世界空间坐标乘上得到的上一帧摄像机 VP 矩阵,获得上一帧的 NDC 空间坐标
  7. 好了到这里上一帧的 NDC 空间坐标、当前帧的 NDC 空间坐标都有了,就可以轻松得到每一个每个像素点上一帧挪动到这一帧的位置向量(Motion Vector)
  8. 根据这个向量计算运动模糊

对于第④步的坑: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:运动模糊相关推荐

  1. Matlab图像复原(运动模糊、散焦模糊)

    图像退化 图像退化的因素各种各样,但最主要的就是在得到图像,传送过程和保存的时候导致的,还有由于形成图像系统的不同.想要拍的物体和相机之间的相对运动,一些空气媒介等等都会让图像变得模糊不清楚.混入噪声 ...

  2. 如何消除摄影中的运动模糊?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|计算机视觉life 如果你试过去拍摄一些运动场景,例如拍 ...

  3. 使用后期处理效果实现运动模糊

    1.介绍 在电子游戏中模拟速度的一种最好的方法就是使用运动模糊.运动模糊是游戏中最重要的效果之一,尤其是在赛车游戏中,因为它可以增加真实感和速度感.运动模糊还可以帮助游戏画面的平滑,尤其是对于帧速小于 ...

  4. matlab 维纳滤波恢复运动模糊,运动模糊恢复专题

    相关背景知识 1.运动模糊的定义 wiki百科上的定义是:运动模糊或运动模糊(motion blur)是静态场景或一系列的图片像电影或是动画中一样快速移动,使物体产生明显运动痕迹. [图片上传失败.. ...

  5. 【youcans 的 OpenCV 例程 200 篇】104. 运动模糊退化模型

    欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中 欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中 [youcans 的 OpenCV 例程 2 ...

  6. Win8 Metro(C#)数字图像处理--2.50图像运动模糊

    原文:Win8 Metro(C#)数字图像处理--2.50图像运动模糊  [函数名称] 图像运动模糊算法    MotionblurProcess(WriteableBitmap src,int  ...

  7. ae运动模糊怎么调整_如何快速成长为一名AE高手?

    Adobe After Effects是一种数字视觉效果.动态图形和合成应用程序,用于电影制作.视频游戏和电视制作的后期制作过程.最常见的操作便是AE用于抠像,跟踪,合成和动画. 图片来自:apple ...

  8. python实现运动模糊图像_OpenCV+Python实现图像运动模糊和高斯模糊

    原标题:OpenCV+Python实现图像运动模糊和高斯模糊 运动模糊:由于相机和物体之间的相对运动造成的模糊,又称为动态模糊 OpenCV+Python实现运动模糊,主要用到的函数是cv2.filt ...

  9. 两个一样的图像相除会怎么样_【壮凌自动化分析】一种动力电池生产中基于图像运动模糊的速度检测方法...

    一种动力电池生产中基于图像运动模糊的速度检测方法 1.西南大学 电子信息工程学院,重庆 400715) 2.非线性电路与智能信息处理重庆市重点实验室,重庆 400715) 1.当前背景与成熟方法介绍 ...

最新文章

  1. httpTomcat
  2. 【设计模式】-写在前面
  3. 积跬步,聚小流------html知识大纲归纳总结
  4. 键盘鼠标录制哪个好用_好看好用还不贵的那种键盘鼠标真的有吗?这次还真让我碰到了...
  5. mysql备份一个表到ftp_备份部分mysql表并上传至指定ftp服务器目录中
  6. 遥控器按键不灵的修复方法
  7. python博客开发教程_Django 博客开发教程 12 - 评论
  8. 前端学习(2328):angular之模板
  9. npm 编译打包vue_从零到一教你基于vue开发一个组件库
  10. leetcode880.DecodedStringatIndex
  11. linux下哪个输入法最好,[最好]linux下输入法→linux下输入法
  12. 2021-CVPR-Inpainting论文导读
  13. java实现生成pdf_Java 生成 PDF 文档
  14. 不带www的域名强制跳转到www域名,Nginx服务器rewrite重写
  15. 哔哩哔哩 机器人历险记_机器人历险记谁演的,机器人历险记的扮演者罗德尼资料介绍-易看TV...
  16. JAVA 短链码生成工具类
  17. 5个文献免费下载神器
  18. 【无线串口模块应用实例】防小人不防君子的智慧安防监测系统
  19. win10电脑发现不了华为share_huaweishare打开 电脑找不到手机
  20. 从张鑫旭的demo中,我学到了图像拉伸的原理

热门文章

  1. 零基础学python难吗-学习python12小时后,告诉你,学python真没你想的那么难!
  2. python怎么读取csv文件-python怎么读取csv文件
  3. 爬虫python能做什么-Python除了爬虫,还能干啥?
  4. python教程视频下载-Python学习精品教程,视频书籍打包下载
  5. 带网格的_【我看身边的网格化】申港街道:一人一板穿梭楼宇小巷 一网一格解决百姓问题...
  6. JavaScript错误与异常
  7. 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)...
  8. java 线性的排序算法_数据结构之排序算法Java实现(9)—— 线性排序之 基数排序算法...
  9. java.lang.ClassNotFoundException: org.openxmlformats.schemas.wordprocessingml.x2006.main.impl.CTPIm
  10. Docker镜像源更改