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.pswp.cn/news/370707.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370707.shtml
英文地址,請注明出處:http://en.pswp.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安…