提及深度,朋友們必定都不陌生。爲了解決渲染場景時哪部分可見,哪部分不可見的問題(便可見性問題,也被稱爲隱藏面移除問題,hidden surface removal problem,從術語這個角度看,技術的發展有時也會帶動心態向積極的方向的變化),計算機圖形學中常使用畫家算法或深度緩衝的方式。html
這也是在處理可見性問題時的兩個大方向上的思路:Object space方式和Image space方式。在後文的描述中,各位應該可以體驗到這兩種方式的異同。
下圖就是在Unity引擎中將深度緩衝的數據保存成的圖片。算法
而利用深度圖咱們又能夠實現不少有趣的視覺效果,例如一些頗有科幻感的效果等等。編程
不過在說到這些有趣的效果以前,咱們先來看看所謂的可見性問題和深度圖的由來吧。學習
在計算機圖形學中,有一個很重要的問題須要解決,即可見性問題。由於咱們要將一個3D模型投影到2D的平面上,這個過程當中哪些多邊形是可見的,哪些是不可見的必需要正確的處理。
按照人類的天性,一個最簡單的解決方案就是先繪製最遠的場景,以後從遠及近,依次用近處的場景覆蓋遠處的場景。這就比如是一個畫家畫畫同樣。
(圖片來自維基百科)
而計算機圖形學中的畫家算法的思想即是如此:測試
首先將待渲染的場景中的多邊形根據深度進行排序。spa
以後按照順序進行繪製。code
這種方法一般會將不可見的部分覆蓋,這樣就能夠解決可見性問題。
可是,世界上就怕可是二字,使用畫家算法這種比較樸素的算法的確能解決簡單的可見性問題,不過遇到一些特殊的狀況就無能爲力。例以下面這個小例子:htm
在這個例子中,三個多邊形A、B、C互相重疊,那麼到底如何對它們進行排序呢?此時咱們沒法肯定哪一個多邊形在上,哪一個多邊形在下。在這種狀況下,多邊形做爲一個總體進行深度排序已經不靠譜了,所以必須用一些方法對這些多邊形進行切分、排序。對象
咱們能夠看到,這種方式是以場景中的對象或者說多邊形爲單位進行操做的。於是經常被稱爲Object space 方法或者稱爲Object precision 方法,我我的更喜歡後者這個稱呼,由於這是一個關於操做精度的區別。這種方式主要是在對象或多邊形這個級別的,即對比多邊形的先後關係。除了畫家算法以外,背面剔除也是Object Space的方法。它經過判斷面的法線和觀察者的角度來肯定哪些面須要被剔除。排序
既然做爲總體互相重疊致使難以排序,那麼是否能夠對多邊形進行切分呢?Newell算法早在1972年就已經被提出了,因此算不得是什麼新東西。可是它的一些思路仍是頗有趣的,倒也值得咱們學習。
和畫家算法同樣,Newell算法一樣會按照深度對場景內的對象進行排序並對排序後的多邊形從遠及近的依次繪製,不過有時會將場景內的多邊形進行切割成多個多邊形,以後再從新排序。
簡單來講,首先咱們能夠將參與排序的結構定義爲各個多邊形上頂點的最大Z值和最小Z值[Zmax,Zmin]。
咱們會以多邊形上距離觀察者最遠的頂點的Z值對場景內的多邊形進行一個粗略的排序(由於此時只是依據每一個多邊形距離觀察者最遠的那一個頂點的Z值進行排序),這樣咱們就得到了一個多邊形列表。
以後,取列表中的最後一個多邊形P(它的某個頂點是距離觀察者最遠的頂點)和P以前的一個多邊形Q,以後經過對比來肯定P是否能夠被寫入幀緩衝區。
這個對比簡單的說就是是否符合下面這個條件:
多邊形P的Zmin > 多邊形Q的Zmax
若是符合該條件,則P不會遮蓋Q的任何部分,此時能夠將P寫入幀緩衝區。
即使答案是否,P和Q也有可能不發生遮蓋。例如它們在x、y上並沒有重疊。可是,Q仍是有可能會被分割成若干個多邊形{Q1,Q2...}。此時有可能會針對下面的幾條測試結果,對最初的多邊形列表進行從新排序(也有可能生成新的多邊形,將新的多邊形也歸入最初的列表中)並決定渲染的順序。
多邊形P和多邊形Q在X軸上是否可區分?
多邊形P和多邊形Q在Y軸上是否可區分?
多邊形P是否徹底在多邊形Q的後方?
多邊形Q是否徹底在多邊形P的前方?
判斷兩個多邊形的投影是否重疊?
若是這幾條測試所有都沒有經過,則須要對Q或P進行切割,例如將Q切割成Q一、Q2,則Q1和Q2將被插入多邊形列表代替Q。
可是,咱們能夠發現,這種對深度進行排序後再依次渲染的方式會使得列表中多邊形的每一個點都被渲染,即使是不可見的點也會被渲染一遍。所以當場景內的多邊形過多時,畫家算法或Newell算法會過分的消耗計算機的資源。
正是因爲畫家算法存在的這些缺點,一些新的技術開始獲得發展。而深度緩衝(depth buffer或z-buffer)就是這樣的一種技術。Depth Buffer技術能夠看做是畫家算法的一個發展,不過它並不是對多邊形進行深度排序,而是根據逐個像素的信息解決深度衝突的問題,而且拋棄了對於深度渲染順序的依賴。
於是,Depth Buffer這種方式是一種典型的Image space 方法,或者被稱爲Image precision方法,由於這種方式的精度是像素級的,它對比的是像素/片元級別的深度信息。
這樣,除了用來保存每一個像素的顏色信息的顏色緩衝區以外,咱們還須要一個緩衝區用來保存每一個像素的深度信息,而且兩個緩衝區的大小顯然要一致。
該算法的過程並不複雜:
首先,須要初始化緩衝區,顏色緩衝區每每被設置爲背景色。而深度緩衝區則被設爲最大深度值,例如通過投影以後,深度值每每在[0,1]之間,所以能夠設置爲1。
通過光柵化以後,計算每一個多邊形上每一個片元的Z值,並和對應位置上的深度緩衝區中的值做比較。
若是z <= Zbufferx(即距離觀察者更近),則須要同時修改兩個緩衝區:將對應位置的顏色緩衝區的值修改成該片元的顏色,將對應位置的深度緩衝區的值修改成該片元的深度。即:Colorx = color; Zbufferx = z;
下面是一個小例子的圖示,固然因爲沒有通過標準化,所以它的各個座標和深度值沒有在[0-1]的範圍內,不過這不影響:
第一個多邊形,深度都爲5。
第二個多邊形,它的三個頂點的深度分別爲二、七、7,所以通過插值,各頂點之間的片元的深度在[2-7]之間,具體如右上角。咱們還能夠看到右下角是最後結果,紫色的多邊形和橘色的多邊形正確的互相覆蓋。
衆所周知,渲染最終會將一個三維的物體投射在一個二維的屏幕上。而在渲染流水線之中,也有一個階段是頂點着色完成以後的投影階段。不管是透視投影仍是正交投影,最後都會藉助一個標準立方體(CVV),來將3維的物體繪製在2維的屏幕上。
咱們就先來以透視投影爲例,來計算一下通過投影以後某個頂點在屏幕空間上的座標吧。
因爲咱們使用左手座標系,Z軸指向屏幕內,所以從N到F的過程當中Z值逐漸增大。依據類似三角形的知識,咱們能夠求出投影以後頂點V在屏幕上的座標。
咱們能夠經過一個實際的例子來計算一下投影后點的座標,例如在一個N = 1,v的座標爲(1,0.5,1.5),則v在近裁剪面上的投影點v'的座標爲(0.666,0.333)。
可是,投影以後頂點的Z值在哪呢?而在投影時若是沒有頂點的深度信息,則兩個不一樣的頂點投影到同一個二維座標上該如何斷定使用哪一個頂點呢?
(v1,v2投影以後都會到同一個點v')
爲了解決保存Z值的信息這個問題,透視變換借助CVV引入了僞深度(pseudodepth)的概念。
即將透視視錐體內頂點的真實的Z值映射到CVV的範圍內,即[0,1]這個區間內。須要注意的是,CVV是左手座標系的,所以Z值在指向屏幕內的方向上是增大的。
爲了使投影后的z'的表達式和x’、y‘的表達式相似,這樣作更易於用矩陣以及齊次座標理論來表達投影變換,咱們都使用z來作爲分母,同時爲了計算方便,咱們使用一個z的線性表達式來做爲分子。
以後,咱們要作的就是計算出a和b的表達式。
在CVV中處於0時,對應的是透視視錐體的近裁剪面(Near),z值爲N;
0 = (N * a + b) / N
而CVV中1的位置,對應的是視錐體的遠裁剪面(Far),z值爲F;
1 = (F * a + b) / F
所以,咱們能夠求解出a和b的值:
a = F / (F - N) b = -FN / (F - N)
有了a和b的值,咱們也就求出來視錐體中的Z值映射到CVV後的對應值。
最後來講說Unity中的Depth,它的值在[0,1]之間,而且不是線性變化的。
所以有時咱們須要在Shader中使用深度信息時,每每須要先將深度信息轉化成線性的:
float linearEyeDepth = LinearEyeDepth(depth);
或
float linear01Depth = Linear01Depth(depth);
咱們根據Unity場景中的深度信息渲染成一張灰度圖,就獲得了本文一開頭的深度圖。
-分割線-
最後打個廣告,歡迎支持個人書《Unity 3D腳本編程》