Reversed-Z详解

  在3D渲染管线中,Z这个家伙几乎无处不在,如Z-Buffer,Early-Z,Z-Cull,Z-Test,Z-Write等等,稍有接触图形学的人都会对这些术语有所耳闻。

  那么Z到底是什么呢?首先Z当然可以是任意坐标系下的z坐标值,但我们这里要说的Z值,就是深度值,上面几个包含Z的术语里面的Z也都是深度值的意思,深度值是物体变换到屏幕空间后的z坐标的值,因为NDC空间转屏幕空间时并不会改变z值,所以也可以说是NDC空间中z坐标的值,有些读者可能认为在屏幕空间中Z值已经不存在了,这也是有道理的,因为屏幕是一个2d空间,没有z轴,但我们在这里不做2d,3d区别,认为都有z轴。在DirectX中,Z值得取值范围是[0,1],在OpenGL中,其取值范围为[-1,1],这篇拟在DirectX环境下讨论Z。

  Z值的推导请参见:

http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c10123/Deriving-Projection-Matrices.htm

  这里我们直接用上文中的一个结果(建议没推导过的读者按照这一篇的思路推导一遍,必定会受益匪浅), 即Z值在透视投影后的结果:

$$ZZ_{c}={\frac{f}{f-n}Z_{c}}-{\frac{fn}{f-n}}$$

  上面的方程中,$Z$即我们要求的深度值,$Z_{c}$是物体在Eye Space中的z坐标,f是视锥体远裁剪平面在Eye Space中的z坐标,n是视锥体近裁剪平面在Eye Space中的z坐标。由上式可求得($ZZ_{c}$其实是Clip Space中的z值,除以$Z_{c}$就是透视除法,得到NDC空间的z值,也即是深度值Z):

 $$Z={\frac{f}{f-n}}-{\frac{fn}{(f-n)*Z_{c}}}\quad ①$$

  对于$Z_{c}$,我们可以证明其关于物体在World Space中的z值$Z_{w}$为线性关系,那么根据上式可知$Z$与$Z_{c}$、$Z_{w}$皆不为线性关系。简单起见,我们取f=1000,n=0.01,有:

 $$Z≈-{\frac{0.01}{Z_{c}}}+1\quad②$$

其函数图像如下($Z_{c}>0$):

图1

 

  图中A点表明了$Z_{c}$∈[0.01,0.1]的物体占用了十分之九(0~0.9)的深度值,这说明在z轴方向上与相机距离为0.1到1000的物体只用到了十分之一(0.9~1.0)的深度值。这个结果是令人印象深刻的,因为Z值的分布太不均匀了,就好像世界上的绝大部分钱都被一个人占有了一样。那Z值的分布情况对于3d渲染来说重要吗?它意味着什么呢?

  深度值的不均分分配会导致非常严重的后果,那就是Z-Fighting。深度值的取值范围是[0,1],但这并不代表它存到Z-Buffer里面后也一定是[0,1]的浮点数,事实上在过去很长一段时间乃至现在很多时候,深度值被保存在16位或者24位的无符号整数中。这里我们用范围更小的16位来存储深度值,因为这能更好的凸显出问题。当深度值存储为16位无符号整型格式时,其取值范围是[0,65535],现在我们来算一算当深度值为65534时,$Z_{c}$是多少?65534映射到[0,1]中,值为65534/65535。连同f=1000,n=0.01代入①式(①比②可获得更精确的结果)可解得:$Z_{c}$≈395.9005401718437≈395.9,这说明在Eye Space中在z轴方向上距离相机395.9到1000的物体的深度值都是65535!当两个物体拥有同样的深度值时,就会产生非常丑陋的Z-Fighting(详见:https://en.wikipedia.org/wiki/Z-fighting):

(相同的深度值导致GPU不能正确分辨哪个在前,哪个在后)。

    在3d渲染中,应该尽可能的避免产生Z-Fighting,即应该尽可能的改善深度值分布的均匀程度。提高用来保存深度值类型的精度可以起到改善z值冲突的情况,比如用24位甚至32位的数据类型来存储深度会比16位好很多,但由于硬件条件的限制和Z值的非线性增长,目前来说不可能用太多位的硬件出现。有的人也许会想到用浮点数来保存深度值,但其实这毫无作用的,甚至可以说更为浪费,因为对于32位浮点数,其尾数(Mantissa)只有23位二进制数,规格化浮点数加上一位保留位也只有24位,这与24位无符号整数表示的精度是一样的,而浮点数还多使用了8位来存储其他信息。另外,虽然浮点数本身表示的范围更广,但我们知道深度值的范围不过为[0,1],当我们用浮点数来存储深度值时,当然不会再去做映射,这样,深度值其实只占到了范围在[0,1]的浮点数所占的精度,这势必就更少了,不过好在浮点数的精度分布也主要分布在0值附近,0值附近的符点数拥有更好的精度,但不管怎样,目前来说想依靠浮点数来改善状况是不可取的。

  除了提高Z-Buffer的精度以外,还有一些方法也可以改善Z值冲突的情况,如增大近裁面与相机位置z值距离(即n值)就是一种方法。对①式 我们取n=0.1,f=1000(不变),有:

$$Z≈-\frac{0.1}{Z_{c}}+1$$

其图像如下:

图2

  对比图1,图2的情况好了很多,对比两个图中的点A,前0.9的深度值表示的范围从0.1扩大到了1,说明有更多的深度值用来表示$Z_{c}$比较大的情况,如果还以16位无符号整数来存储深度值,计算后可得$Z_{c}$在区间[868,1000]时共享65535这个深度值,这比[396,1000]的冲突少了非常多,降低了出现Z-Fighting的概率。而我们仅仅是将n从0.01提高到0.1而已,这对一般的应用场景几乎不会产生影响。

  既然如此,我们将n值继续增大,比如取n=100,会怎样呢?我们将n=100,f=1000(不变)代入1式得:

 $$Z=-\frac{1000}{9Z_{c}}+\frac{10}{9}$$

图像如下(我必须把x轴压缩400倍才能截个图):

图3

  可以看到0到0.9的深度值已经可以表示到大约=600的时候了,要知道n=0.01的时候, 0.9的深度值$Z_{c}$只能表示到0.1;n=0.1的时候$Z_{c}$只能表示到1。依然将深度值存入到无符号整型中,我们可以计算出当物体的$Z_{c}$∈[999.863,1000]时,它们才共用65535这个深度值,通过取n=100我们很好地改善了Z值的分布情况。至少看起来已经是个很好——甚至可以说近乎完美的办法了。但是,事实并非如此,由于取得n=100,我们舍弃了整个$Z_{c}$∈[0,100]的物体,我们将永远看不到那些离相机z轴距离少于100的物体!增大近裁剪面的值以换取深度值的分布均匀程度,难言利弊得失。

    难道就没有更好的改善深度值分布的办法了吗?当然有了,办法就是神奇的Reversed-Z,Reversed-Z的做法其实是很简单的,即将原本近裁剪平面映射到深度值0,远裁剪平面映射到深度值1的映射关系反过来,让近裁剪平面映射到深度值1,远裁剪平面映射到深度值0。即将[n,f]映射到[1,0],按照上文给出的投影矩阵推导链接中的方法,我们可以推导出Reversed-Z的情况下Z与$Z_{c}$的关系(其实就是①式中n与f互换):

  $$Z={\frac{n}{n-f}}-{\frac{fn}{(n-f)*Z_{c}}}$$

  我们取n=0.1,f=1000,有:

$$Z≈\frac{0.1}{Z_{c}}$$

函数图像如下:

图4

    看到上面的图,细心的读者可能会发现,这不跟图2一样嘛,都是$Z_{c}$=1的时候,深度值Z就用了十分之九(0.9)了,不过是前者是[0, 0.9],这里是[0.1, 1]而已,有区别吗?如果我们还是以无符号整型来存储深度值,的确对我们达成目的没有帮助,依然是靠近近裁剪平面的少数物体占据了大多数深度值。但是我说过Reversed-Z是神奇的,它的神奇之处是当它搭配上我前面否定过的浮点数时,Reversed-Z在"提高深度值均分分布程度" 这件事上就变得非常有效了。

    让我们回到浮点数,前面有提到过 "0值附近的符点数拥有更好的精度",这是有依据的,浮点数具体介绍请参考维基百科:https://en.wikipedia.org/wiki/IEEE_floating_point,这里以单精度符点类型做简单说明。规约化单精度浮点数的有效位数只有7位(实际是7点多位,这里简单起见取7),当一个浮点数小于1的时候,它可以确保有6位小数位是精确的,也就是说,在(0,1)这个开区间内至少可以包含999999(6位)个误差允许的单精度浮点数,1~9同理,但由于非规约化浮点数(主要是在0值左右)的存在,使得(0,1)这个区间内的浮点数个数要比(1,2),(2,3)…(9,10)这些区间内的符点数要多。在(10, 11)这个区间内,由于整数位占去了两位,所以这个区间内至少只可以包含99999(5位)个有效单精度浮点数,以此类推,(100,101)开区间内包含9999个有效单精度浮点数,(1000,1001)开区间内包含999个有效单精度浮点数等等,当数量级来到[1000000,1000001]时(注意这里是闭区间),这个区间内能保证有效的单精度浮点数不过就两个:1000000与1000001本身。这说明浮点数的分布与深度值的分布一样是不均与的,越靠近0的浮点数分布越密集,越远离0的浮点数分布越稀疏:

浮点数分布情况图

  当我们用正常的Z值系统([n,f]映射到[0,1])与浮点数配合时,符点数没有任何帮助(原谅这个不一样的画风,由于我还不太会使用GeogeBra作图,我把本文的主要参考文章Depth Precision Visualized的图拿过来用了):

图5

  图5中z1到z2这么远的距离依然只共享一个深度值0.99。

    

但当我们将Reversed-Z([n,f]映射到[1,0])与浮点数结合起来,情况就变成了:

图6

随着$Z_{c}$的增大,深度值Z的降幅越来越小,看似又要陷入精度不够的死胡同,但浮点数的分布规律恰好弥补了这一不足,使得较大的也有足够的精度表示,图6中z1到z2比之图5中多获得了5个深度值。这样,距离相机近和远的物体分得的深度值就比较平均了,变相的实现了"改善深度值分布状况"这一目的,从而也达到了降低Z-Fighting出现的概率(是的,虽然Reversed-Z这么神奇,但Z-Fighting还是不能完全避免的,虽然概率已经降到很低)。

作为依赖Unity引擎的开发者,很高兴看到Unity在其5.5以及以后的版本中引入了Reversed-Z的做法,在这里也提醒一下大家以后为Unity写shader的时候,如果用到深度值Z,一定要记得 [n,f] 是映射到[1,0],否则就会写出错误的效果J

 

参考与说明:

本文参考:Depth Precision Visualized,对Reversed-Z进行思考与分析,希望能对读者有所帮助。

文中的函数图像使用GeoGeBra软件绘制,公式用LaTex 语法写成。

转载于:https://www.cnblogs.com/jackmaxwell/p/6851728.html

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

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

相关文章

pyqt开发的程序模板_小程序定制开发和模板开发要多少钱?有什么区别?

到现在,小程序开发已经有了1年多的历史,已经达到百万数量级。无论是小程序商城还是小程序游戏,其开发方式不外乎两种,一种是定制开发,另一种是模板开发。对于很多初次接触小程序的客户来说,还不知道小程序的…

实现字符串的编码转换,用以解决字符串乱码问题

引起乱码的情况很多~实质上 主要是字符串本身的编码格式 与程序所需要的编码格式不一致导致的。要解决乱码其实很简单, 分2步 : 1:获取到字符串 本身的编码 2:改变字符串编码 (本身编码 -> 新编码) 话不…

python运行原理_Python线程池及其原理和使用(超级详细)

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。 线程池在系统启动时即创建大量空闲的线程…

Google Guava缓存

这篇文章是我在Google Guava上系列文章的续篇,这次涵盖了Guava Cache。 与HashMap或ConcurrentHashMap相比,Guava Cache提供了更大的灵活性和功能,但不像使用EHCache或Memcached那样繁重(就此而言,它很健壮&#xff0c…

html 三列布局(两列自适应,一列固定宽度)

不做过多解释:主要是记录一个完整的布局样式,实现页面大致三列其中左右两列是自适应宽度,中间固定宽度效果。 不多少代码奉上: CSS样式代码: /*********************公共标签样式********************//************…

jsp常用动作

jsp:include 动态包含; jsp:forward 转发; jsp:useBean 实例化bean对象; jsp:setProperty 设置一个属性值 jsp:getProperty 获取一个属性值 jsp:param 动态传参数; jsp:plugin 生成一个插件 jsp:useBean 实例化一个对象…

单曲循环 翻译_歌单 | 单曲循环amp;热评

December2020/12/ 写在前面的话 /本来打算在跨年的时候才更文,但是吧又觉得空出这最后一个月有点苍白,然后最近一直夜半网抑云(敏感ing)就想到可以做一期分享歌单的推文,分享一些最近听得频繁的歌曲(还不是刷抖音刷出来的)。《暧昧》// 王菲徘…

python的字符串内建函数

python的字符串内建函数 字符串方法是从python1.6到2.0慢慢加进来的——它们也被加到了Jython中。 这些方法实现了string模块的大部分方法,如下表所示列出了目前字符串内建支持的方法,所有的方法都包含了对Unicode的支持,有一些甚至是专门用…

休息使用Jersey –包含JAXB,异常处理和客户端程序的完整教程

最近,我开始使用Jersey API开发一个Restful Web服务项目。 在线提供了一些教程,但是我遇到了异常处理方面的一些问题,而且在使用JaxB和提供异常处理方法的完整项目中找不到任何地方。 因此,一旦我能够使用带有异常处理和客户端程序…

python基于web可视化_独家 | 基于Python实现交互式数据可视化的工具(用于Web)

转自:数据派ID:datapi 作者:Alark Joshi 翻译:陈雨琳 校对:吴金笛 本文2200字,建议阅读8分钟。 本文将介绍实现数据可视化的软件包。 这学期(2018学年春季学期)我教授了一门关于数据…

SASS简介及使用方法

一、什么是Sass Sass (Syntactically Awesome StyleSheets)是css的一个扩展开发工具,它允许你使用变量、条件语句等,使开发更简单可维护。这里是官方文档。 二、基本语法 1)变量 sass的变量名必须是一个$符号开头,后面紧跟变量名…

【转】Java方向如何准备BAT技术面试答案(汇总版)

原文地址:http://www.jianshu.com/p/1f1d3193d9e3 这个主题的内容之前分三个篇幅分享过,导致网络上传播的比较分散,所以本篇做了一个汇总,同时对部分内容及答案做了修改,欢迎朋友们吐槽、转发。因为篇幅长度和时间的原…

numpy维度交换_“lazy”的transpose()函数——从numpy 数组的内存布局讲起

1 数组的两种内存布局方式行优先与列优先首先我们回顾一下,矩阵数据在内存中的两种布局方式:行优先(row-major):以行为优先单位,在内存中逐行存储/读取;对于多维,意味着当线性扫描内…

云耀服务器切换系统,【计算】云耀服务器-常见操作汇总指南

通过上期的介绍,相信大家对于云耀云服务器的基本知识有了一个了解。云耀云服务器是一个具备独立、完整的操作系统和网络功能,可快速搭建简单应用的新一代云服务器。接下来,本期为大家带来关于云耀云服务器使用中的一些简单方法和小技巧。1.云…

机器学习应该准备哪些数学预备知识?

转 https://www.zhihu.com/question/36324957 https://www.zhihu.com/question/36324957/answer/139408269 机器学习应该准备哪些数学预备知识? 数据分析师,工作中经常使用机器学习模型,但是以调库为主。 自己一直也在研究算法,也…

react usecontext_Vue3原理实战运用,我用40行代码把他装进了React做状态管理

前言vue-next是Vue3的源码仓库,Vue3采用lerna做package的划分,而响应式能力vue/reactivity被划分到了单独的一个package中。如果我们想把它集成到React中,可行吗?来试一试吧。使用示例话不多说,先看看怎么用的解解馋吧…

Spring MVC –自定义RequestMappingHandlerMapping

在xml bean定义文件中使用<mvc&#xff1a;annotation-driven />配置Spring MVC时&#xff0c;在内部将一个名为RequestMappingHandlerMapping的组件注册到Spring MVC。 该组件或通常是HandlerMapping组件负责将请求URI路由到处理程序&#xff0c;这些处理程序是使用Requ…

css的三个特性 背景透明设置

关于行内元素&#xff08;补充一点&#xff09; 行内元素只能容纳文本或其他行内元素。&#xff08;a特殊a里面可以放块级元素&#xff09; 例子&#xff1a; 关于行高tip: 选择器的嵌套层级不应大于3级&#xff0c;位置靠后的限定条件应尽可能的精确。 属性定义必须另起一行…

比较容易犯的一些智障错误(不定时修改)

无论在什么学习中&#xff0c;在成长的过程中&#xff0c;注定要犯一些错误&#xff0c;有些比较高级的错误&#xff0c;有些是比较智障的错误。那么在oi的学习中&#xff0c;我们最讨厌的就是一些智障的小错误&#xff0c;因为如果是大错误的话一般情况下在测试样例的时候都是…

ccs安装多版本编译器离线_大数据分析:学习工具JDK,在线安装指南

hadoop是使用Java语言开发的并且Hadoop运行需要有Java环境的支持&#xff0c;因此在安装hadoop之前需要安装Java开发环境即JDK(Java Development Kit)。安装前首先向大家介绍以一下本文会用到的几个词&#xff1a;JAVA_HOME:一是为了方便引用&#xff0c;比如&#xff0c;JDK安…