Z深度相关知识

渲染中深度信息很重要,但是也很让人迷惑,透视投影是什么,为什么要做透视除法,view空间,clip空间,ndc空间对应的z值又代表什么,这里简单总结下。

一.顶点变换的完整过程

二.View空间下的顶点和Z值

        输入顶点在经过MV矩阵变换后,变化到View空间,也就是相机视锥空间(上图中的Eye Space),在这个空间内的z值代表着顶点到摄像机Z方向的距离(也就是相机到幕布的垂直距离,下图中的Depth而不是Distance),在View空间内Z_{view}是线性的 

在该空间内,假设有一点(X_{view}Y_{view}Z_{view}) ,转成齐次坐标后为(X_{view}Y_{view}Z_{view},1)

 三.Clip空间下的顶点和Z值

        在这一步,我们暂且先放下,只假设该空间内的顶点为(X_{clip},Y_{clip},Z_{clip},W_{clip})下看看接下来我们目标NDC空间所需的顶点和Z值长什么样。

 四.NDC空间下的顶点和Z值

        NDC标准空间下的顶点值我们记为(X_{ndc}Y_{ndc}Z_{ndc}, 1).在渲染管线NDC空间中,X_{ndc}

Y_{ndc}范围为-1到1, Z_{ndc}在在openGL环境下是范围是-1到1,DirectX中是0到1。

X_{ndc}X_{view}Y_{ndc}Y_{view}之间是存在联系的:

 具体推导过程详见这篇文章:透视投影矩阵的推导 - bluebean - 博客园

这里单说结论:其中x^{''}X_{ndc},x即X_{view},y方向同理,下图中的z为Z_{view}

 已知了他们的关系,假设存在一个矩阵变换,使得View空间中的顶点 (X_{view}Y_{view}Z_{view},1)可以直接转换到(X_{ndc}Y_{ndc}Z_{ndc}, 1),那么该矩阵续满足(变量脑补替换下,xyz对应X_{view}Y_{view}Z_{view}):

我们发现求解

很难找出合适的m00、m02,因为左边x和z是以加法的形式相邻,右边z确成为了x的分母。

解决方法:将右边的以四维列向量表示的坐标每一项乘以z,所以有:

 所以可以求得矩阵为

再根据其他两个特殊条件,Z_{view}在近平面时Z_{ndc}为-1. Z_{view}在远平面时Z_{ndc}为1.

最后求得投影矩阵为

 将这样的矩阵乘以视锥体内的一个顶点坐标(X_{view}Y_{view}Z_{view},1),得到一个新的向量(X_{clip},Y_{clip},Z_{clip},W_{clip}),再将这个向量的每个分量除以第四个分量(Z_{view})(即透视除法),这样就可以得到顶点映射到NDC的坐标(X_{ndc}Y_{ndc}Z_{ndc}, 1),这时Z_{ndc}Z_{view}不再是线性关系,Z_{ndc} 与Z_{view}的倒数是线性关系

五.再梳理一遍

那么以为的个人理解,开始回推一下整个流程。

1.为什么要有透视除法?

因为为了求出消除Z分量影响的投影矩阵

2.为什么要用消除Z分量影响的投影矩阵?

因为这样整个渲染中,一个相机只需要一个投影矩阵,否则,每个顶点都要传入其带有Z分量影响的投影矩阵。

3.流程再梳理:

 (X_{view}Y_{view}Z_{view},1),Z_{view}线性的经过投影矩阵变换 

变为(X_{clip},Y_{clip},Z_{clip},W_{clip}

注意矩阵中的第四行三列,值为1,也即W_{clip} = Z_{view}。大家在很多shader中经常会看到用Clip中w分量去计算一些东西,原理其实就是这样,其实它存的就是View空间中的Z_{view}值大小。

X_{clip},Y_{clip},Z_{clip},W_{clip})= (X_{clip},Y_{clip},Z_{clip},​​​​​ Z_{view}​​),在Clip空间中Z_{clip}Z_{view}还是线性关系

并且X_{clip}Y_{clip}Z_{clip}的范围均为-W_{clip}W_{clip}(Opengl),在Clip空间会进行裁剪,之后

X_{clip},Y_{clip},Z_{clip},W_{clip})经过透视除法,除以W_{clip}后,就得到了NDC空间中的坐标

 (X_{ndc}Y_{ndc}Z_{ndc}, 1)。这时Z_{ndc}Z_{view}不再是线性关系,Z_{ndc} 与Z_{view}的倒数是线性关系

六.Unity中相关的一些问题

1.VertexShader中的顶点输出是啥?

        Vertex Shader的输出在Clip Space,即 (X_{clip},Y_{clip},Z_{clip},​​​​​ Z_{view}​​)

2.Fragment Shader的输入是在什么空间?

        不是NDC,而是屏幕空间Screen Space

我们前面说到Vertex Shader的输出在Clip Space,接着GPU会自己做透视除法变到NDC。这之后GPU还有一步,应用视口变换,转换到Screen Space,输入给Fragment Shader:

(Vertex Shader) => Clip Space => (透视除法) => NDC => (视口变换) => Screen Space => (Fragment Shader)

3.LinearEyeDepth&Linear01Depth

NDC空间中的深度值(深度贴图中存储的值(范围为0到1,Opengl需要从-1到1映射到0到1))如何能反推得到View空间中的深度值呢,具体推倒见该文章Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)_puppet_master的专栏-CSDN博客公式为

Z_{view}(视空间) = 1 / (param1 * Z_{ndc} + param2) 

其中param1 = (N - F)/ NF,param2 = 1 / N。

 Unity自带Shader中关于深度值LinearEyeDepth的处理:

// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}// Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
float4 _ZBufferParams;

_ZBufferParams.z = _ZBufferParams.x / far = (1 - far / near)/ far = (near - far) / near * far

_ZBufferParams.w = _ZBufferParams.y / far = (far / near) / far = 1 / near

我们推导的param1 = _ZBufferParams.z,param2 = _ZBufferParams.w,实际上Unity中LinearEyeDepth就是将透视投影变换的公式反过来,用zbuffer图中的屏幕空间depth反推回当前像素点的相机空间深度值。

下面再来看一下Linear01Depth函数,所谓01,其实也比较好理解,我们上面得到的深度值实际上是真正的视空间Z值,但是这个值没有一个统一的比较标准,所以这个时候依然秉承着映射大法好的理念,把这个值转化到01区间即可。由于相机实际上可以看到的最远区间就是F(远裁剪面),所以这个Z值直接除以F即可得到映射到(0,1)区间的Z值了:

Z(视空间01) = Z(视空间) / F = 1 / (((N - F)/ N) * depth + F / N)

Z(视空间01) = 1 / (param1 * depth + param2),param1 = (N - F)/ N = 1 - F/N,param2 = F / N。

再来看一下Unity中关于Linear01Depth的处理:
 

// Z buffer to linear 0..1 depth
inline float Linear01Depth( float z )
{return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}// Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
float4 _ZBufferParams;

可以看出我们推导的param1 = _ZBufferParams.x,param2 = _ZBufferParams.y。也就是说,Unity中Linear01Depth的操作值将屏幕空间的深度值还原为视空间的深度值后再除以远裁剪面的大小,将视空间深度映射到(0,1)区间。

Unity应该是OpenGL风格(矩阵,NDC等),上面的推导上是基于DX风格的DNC进行的,不过,如果是深度图的话,不管怎么样都会映射到(0,1)区间的,相当于OpenGL风格的深度再进行一步映射,就与DX风格的一致了。

4.unity_CameraProjection和UNITY_MATRIX_P (float4x4)

Unity shader 里面,要获取投影矩阵,有两个变量:unity_CameraProjection (float4x4) 和 UNITY_MATRIX_P (float4x4)。需要注意的是,这两个矩阵的内容实际上不一样。unity_CameraProjection:

 

 UNITY_MATRIX_P:

他们两个的区别是:unity_CameraProjection一直是MainCamera的投影矩阵,并且是OpenGL规范的。

UNITY_MATRIX_P是当前渲染投影矩阵,不一定是OpenGL并且也不一定是MainCamera的

 What's difference between UNITY_MATRIX_P and unity_CameraProjection? - Unity Forum

 5.向量从 camera space 映射到 clip space / screen space

如果想要把一个camera space的向量,从 camera space 映射到 clip space / screen space,需要采取的操作是:用投影矩阵(unity_CameraProjection) 去乘那个向量(向量的齐次坐标 w 分量为0),例如:

6.ComputeScreenPos

inline float4 ComputeScreenPos (float4 pos)
{
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
    o.zw = pos.zw;
    
    return o;
}

首先,该函数传入的参数pos为顶点变换到齐次坐标系下的坐标(ClipSpace),也就是说,在shader中,你需要这么使用:

o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.pos);

ComputeScreenPos返回的值是齐次坐标系下的屏幕坐标值,其范围为[0, w]。那么为什么Unity要这么做呢?Unity的本意是希望你把该坐标值用作tex2Dproj指令的参数值,tex2Dproj会在对纹理采样前除以w分量。当然你也可以像下面代码那样自己除以w分量后进行采样,但是效率不如内置指令tex2Dproj:

pos = UnityObjectToClipPos(v.vertex);
screenPos = ComputeScreenPos(o.pos);
tex2D(_ScreenTex, float2(screenPos.xy / screenPos.w))

七.其它相关知识

1.ZBuffer的精度问题

2.Reverse-Z

3.Z&1/Z

这些相关知识就查看这篇博客吧Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)_puppet_master的专栏-CSDN博客,大佬已经写的非常详细了,在此感谢大佬们的无私分享

参考资料:

透视投影矩阵的推导 - bluebean - 博客园

Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)_puppet_master的专栏-CSDN博客

 写给大家看的“透视除法” —— 齐次坐标和投影 - 简书

实时渲染中的坐标系变换(4):投影变换-2 - 知乎

 掘金

Unity Shader中的ComputeScreenPos函数_linuxheik的专栏-CSDN博客

 What's difference between UNITY_MATRIX_P and unity_CameraProjection? - Unity Forum

八.留个疑问待研究

为什么剪裁是在Clip空间而不是NDC空间?

有知道的大佬可以指教下,知乎上的一个回答是:

 

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246068.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

quartus管脚分配后需要保存吗_掉电保存数据到EEPROM

我想在掉电时保存数据(3 个字节)到 EEPROM 中,用 BOD掉电检测,不知怎样使用。望高手指点:1. 在 BOOT 区设置好 BODEN,BODLEVEL,后软件还要怎样设置?2. 掉电中断是否是产生复位?我的写 EEPROM 程…

matlab 最小二乘法拟合_计量与论文串讲:最小二乘法

点为什么要用R计量经济学作为经济大类下一门非常实用的学科有很强的实践意义。尽管内容庞杂但基本的指导思想却很简单,因此通过一门统计编程语言学习、掌握计量经济学有很大的益处。目前,市面上已经有多款统计编程语言和统计软件,实证分析领域…

ZoomBlur 聚焦模糊效果Shader(URP)

修改自这篇文章【Unity】UniversalRPでカスタムポストプロセスを作る【ZoomBlur】 - Qiita 1. VolumeEditor,用于在UnityVolume中控制自己写的后处理效果 using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal;[System.Seriali…

魅蓝s6启动android密码_魅蓝s6怎么恢复出厂设置?忘记密码怎么办

魅蓝s6怎么恢复出厂设置?忘记密码怎么办?手机是现代生活必不可少的工具之一,在使用时我们常常会忘记手机密码。本次就给大家介绍手机密码忘了怎么恢复出厂设置,快来看看吧。我们知道手机恢复出厂设置的注意事项最重要的就是做好手…

python中怎么调用函数_浅谈Python中函数的定义及其调用方法

一、函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数–封装独立的功能 2.调用函数–享受封装的成果 函数的作用:在开发时,使用函数可以提高编写的效率以及代码…

漫游飞行_魔兽世界:德拉诺时光周 冲声望解锁德拉诺飞行好时机

虽然德拉诺飞行开放已经是6.22版本的事情了,但是目前还是有些玩家还没有解锁。但是本周德拉诺时光周的开放,获取德拉诺飞行声望将会更加简单。德拉诺飞行声望德拉诺飞行解锁需要先知之手(部落是沃金之锋)、觉醒教派和刃牙追猎者三个声望达到崇敬。而在本…

LeetCode695. 岛屿的最大面积(C#)

DFS经典题&#xff0c;两种方法&#xff0c;递归或者用栈 1.递归 public class Solution{public int MaxAreaOfIsland(int[][] grid) {int rows grid.Length;int cols grid[0].Length;int res 0;for(int i 0; i < rows; i){for(int j 0; j < cols; j){res Max(re…

python怎么解释语言_python是解释型语言吗

Python 是解释型的语言吗&#xff1f;它会被编译吗&#xff1f; 这个问题没有想象中那么好回答。和很多人认识世界一样&#xff0c;习惯以一个简单的模型去评判一些事物。而事实上&#xff0c;里面包含了很多很多的细节。通常的说法&#xff0c;编译代表着将一个高级语言转化为…

第一次失效_神兵小将:净化之力失效地魔兵兽,全靠特殊办法,铁心方式真霸气...

神兵小将&#xff1a;净化之力失效地魔兵兽&#xff0c;全靠特殊办法&#xff0c;铁心方式真霸气。在经典动漫神兵小将中&#xff0c;魔兵兽在漫迷眼中属于比较特殊的存在&#xff0c;很多人气指数超高的神兵兽被魔化后却成为阻碍问天前进的高山。因此魔兵兽在很大意义上讲也是…

Python 每日定时查询数据库生成Excel报表,并群发邮件

最近在做游戏打点数据的一些统计处理&#xff0c;写了个Python脚本完成每日定时自动查询生成Excel报表并群发邮件的小功能。 拆解几个需求点&#xff1a; 一.连接数据库并查询 以下是一个查询总注册人数的示例代码&#xff0c;host port db user password等填入自己数据库的…

com口驱动_Ubuntu 安装Nvidia显卡驱动指南

该文档适用于&#xff1a; Ubuntu 14/16/18 三个版本。Nvidia显卡驱动适用于&#xff1a;RTX2080TI/RTX2080/RTX2070/GTX1080TI/GTX1080/GTX1070以及更低级别显卡。本文档旨在帮助大家解决安装Nvidia显卡遇到的常见问题。Nvidia驱动下载地址&#xff1a;https://www.geforce.co…

Unity Built-in Shader转URP Shader 接口查询对照表

本篇文章转自Teofilo Dutra编写的《From Built-in to URP》&#xff0c;其中有很多在写URP管线Shader时需要用到的函数&#xff0c;作为备忘速查表非常实用&#xff0c;所以记录于此。本文经过精简和翻译&#xff0c;不一定适用于大家&#xff0c;可以点击上方链接跳转至作者原…

python class类里给列表排序_python笔记:Class(类)

在学习python代码中&#xff0c;你是否看到过Class这个词&#xff1f;你是否见过__init__这样的代码&#xff1f;有的书里把这部分内容讲的太过“专业”&#xff0c;导致我看了也看不懂。直到看Python Crash Course这本书关于这部分讲解&#xff0c;才明白什么是Class。其实如果…

Unity URP中根据深度重建世界坐标

通过深度值重建世界坐标&#xff0c;可以做出很多有意思的后处理效果&#xff0c;先实现下度值重建世界坐标这个功能。 一.验证重建效果 首先&#xff0c;得先找到一种证明反推回世界空间位置正确的方法。在相机前摆放几个物体&#xff0c;尽量使之在世界坐标下的位置小于1&a…

dubbo yml配置_Spring boot 的profile功能如何实现多环境配置自动切换

通常服务端应用开发需要经过以下几个流程&#xff1a;开发 -> 测试 -> RC验证 -> 上线这就涉及到四个不同的环境&#xff0c;开发环境、测试环境、RC环境以及生产环境&#xff0c;为了避免不同环境之间相互干扰&#xff0c;通常需要独立部署数据库、缓存服务器等&…

Unity中的SystemInfo.deviceUniqueIdentifier 唯一ID

做游戏时可能经常使用SystemInfo.deviceUniqueIdentifier作为用户的唯一ID进行注册登录&#xff0c; 但是你会发现从谷歌商店上下载的自己游戏&#xff0c;和自己从Unity工程中直接打包出来的游戏账号竟然是不一致的&#xff01; 这个坑还是很坑爹的&#xff0c;纠其原因是Sy…

python中转义符的用法大全_Python转义字符及用法

前面已经提到&#xff0c;在字符串中可以使用反斜线进行转义&#xff1b;如果字符串本身包含反斜线&#xff0c;则需要使用“\”表示&#xff0c;“\”就是转义字符。Python 当然不会只支持这么几个转义字符&#xff0c; Python 支持的转义字符如表 1 所示&#xff1a;表 1 Pyt…

sketchup生成面域插件_独家教程 | 快速抓取“高精准”场地信息,康石石教你生成不同“体量”地形...

无论建筑设计还是景观设计&#xff0c;同学们的设计项目都必须依托于场地来进行&#xff0c;通过分析场地的区位范围、地形地势&#xff0c;结合场地的局限性与可能性&#xff0c;才能进一步展开项目设计。可以说&#xff0c;获取场地信息是同学们在作品集创作中最重要的环节之…

Unity URP高度雾效果Shader

实现原理 见这篇文章Unity Shader-深度相关知识总结与效果实现&#xff08;LinearDepth&#xff0c;Reverse Z&#xff0c;世界坐标重建&#xff0c;软粒子&#xff0c;高度雾&#xff0c;运动模糊&#xff0c;扫描线效果&#xff09;_puppet_master的专栏-CSDN博客_shader深度…

无限重启_三星蓝光播放器出现无限自动重启BUG,涉及不少用户及不同型号

三星的蓝光播放器似乎遇到了一个挺严重的BUG&#xff0c;使得不少用户都开机后播放器会自动不停重启。从reddit、ZDNet以及三星技术支持论坛上面的情况来看&#xff0c;这次的问题波及不同型号的播放器&#xff0c;大部分用户遇到的问题都是不停重启。其中一位用户表示:“开机之…