下圖顯示了Unity中透視投影到頂點的變換過程。最左邊的圖顯示了投影變換前觀察空間中的觀察錐和相應頂點位置的結構,中間的圖顯示了應用透視裁剪矩陣後的變換結果,即頂點著色器階段輸出的頂點變換結果,最右邊的圖顯示了底層硬件透視劃分獲得的歸壹化設備坐標(NDC)。需要註意的是,這裏的投影過程是基於Unity對坐標系的假設,即針對觀測空間為右手坐標系的情況,列矩陣用於在矩陣的右側相乘,轉換為NDC後z分量範圍將在【-1,l】之間。在像DirectX這樣的圖形界面中,轉換後的Z分量範圍將在【0,1】之間。如果是在其他圖形界面中,則需要相應地更改壹些計算參數。
下圖顯示了使用正交相機時投影變換的過程。同樣,變換後將得到壹個範圍為【-1,1】的立方體。正交投影中使用的變換矩陣是線性的。
在獲得NDC之後,可以容易地計算深度紋理中的像素值,並且這些深度值對應於NDC中頂點坐標的z分量值。由於NDC中Z分量的範圍是【-1,I】,為了將這些值存儲在圖像中,有必要使用以下公式來映射它們:
其中d對應於深度紋理中的像素值和NDC坐標中z分量的值。
在Unity中,深度紋理可以直接來自真實深度緩存,也可以通過單獨的過程進行渲染,具體取決於渲染路徑和使用的硬件。壹般來說,當使用延遲渲染路徑(包括傳統的延遲渲染路徑)時,可以自然地訪問深度紋理,因為延遲渲染會將這些信息渲染到G-buffer。但是,當無法直接獲取深度緩存時,深度和法線紋理將通過單獨的過程進行渲染。具體實現是Unity會使用著色器替換技術來選擇那些渲染類型(即SubShader RenderType標簽)不透明的對象,並判斷它們使用的渲染隊列是否小於或等於2500(內置的背景幾何體AlphaTest渲染隊列在此範圍內)。如果條件滿足,它將被渲染成深度和正常紋理。因此,為了使對象出現在深度和正常紋理中,有必要在著色器中設置正確的RenderType標記。
在Unity中,您可以選擇讓相機生成深度紋理或深度+普通紋理。當選擇前者時,即只需要單壹深度紋理時,Unity將直接獲取深度緩存或根據前面提到的著色器替換技術選擇所需的不透明對象,並使用其在投射陰影時使用的通道(即LightMode設置為ShadowCaster通道)來獲取深度紋理。如果著色器不包括這樣的過程,則該對象將不會出現在深度紋理中(當然,它不能在其他對象上投射陰影)。深度紋理的精度通常為24位或16位,具體取決於所使用的深度緩存的精度。如果選擇生成深度+法線紋理,Unity將創建壹個具有相同屏幕分辨率和32位(每通道位數)精度的紋理,其中觀察空間中的法線信息將被編碼到紋理通道中,深度信息將被編碼到通道中。在延遲渲染中可以輕松獲得正常信息,Unity只需要合並深度和正常緩存。在正向渲染中,默認情況下不會創建普通緩存,因此Unity底層使用單獨的過程再次渲染整個場景。該過程包含在Unity內置的Unity著色器中。您可以在內置building _ shaders-XXX/default resources/camera-depthnormal texture . shader文件中找到渲染深度和法線信息的過程。
要獲得深度紋理,首先設置相機的深度紋理模式:
然後通過聲明_CameraDepthNormalsTexture變量在Shader中訪問它。
類似地,如果您需要獲得深度+正常紋理,請將相機的深度紋理模式設置為:
然後通過聲明_ CameraDepthN ormalsTexture變量在Shader中訪問它。
您還可以組合這些模式,以便相機可以同時生成深度和深度+法線紋理:
在Shader中訪問深度紋理_ cameradepttexture後,我們可以使用當前像素的紋理坐標對其進行采樣。在大多數情況下,我們可以直接用tex2D函數采樣,但在某些平臺上(如PS3 PSP2),我們需要壹些特殊的處理。Unity為我們提供了統壹的宏SAMPLE_DEPTH_TEXTURE來處理這些因平臺差異而導致的問題。我們只需要使用Shader中的SAMPLE_DEPTH_TEXTURE宏對深度紋理進行采樣,例如:
其中,i.scrPos是通過在頂點著色器中調用ComputeScreenPos(o。pos)獲得的屏幕坐標。這些宏的定義可以在Unity內置的HLSLSupport.cginc文件中找到。
當通過紋理采樣獲得深度值時,這些深度值通常是非線性的,這來自於透視投影中使用的裁剪矩陣。但是,在我們的計算過程中,通常需要線性深度值,也就是說,我們需要將投影的深度值轉換到線性空間中,例如透視空間中的深度值,我們只需要反轉頂點轉換的過程。以透視投影為例,推導了如何從深度紋理中的深度信息計算視角空間中的深度值。
當我們使用透視投影的裁剪矩陣來變換觀察空間中的頂點時,裁剪空間中頂點的z和w分量為:
其中,遠和近分別是距遠剪裁平面和近剪裁平面的距離。然後,我們可以通過齊次除法得到NDC下的z分量:
深度紋理中的深度值由NDC通過以下公式計算:
從上述公式中,我們可以推導出d表示的表達式:
因為在Unity使用的查看空間中,攝像機對應的z值全部為負,所以為了得到深度值的正表示,我們需要對上述結果進行反轉,最終結果如下:
它的取值範圍是視錐深度範圍,即【近、遠】。如果我們想得到【0,l】範圍內的深度值,我們只需要將上面的結果除以遠即可。這樣,0表示該點與相機處於同壹位置,1表示該點位於觀察錐的遠剪裁平面中。結果如下:
事實上,Unity提供了兩個輔助函數來為我們執行上述計算過程:LinearEyeDepth和LinearOlDepth。LinearEyeDepth負責將深度紋理的采樣結果轉換為透視空間中的深度值,這就是我們上面得到的結果。Linear01Depth將返回壹個在【0,1】範圍內的線性深度值,這就是我們上面得到的結果。這兩個函數使用內置的_ZBufferParams變量來獲取遠裁剪平面和近裁剪平面之間的距離。
如果需要獲取深度+法線紋理,可以直接使用tex2D函數對_ cameradepthnormalstructure進行采樣,以獲取其中存儲的深度和法向信息。Unity為我們提供了壹個輔助函數來解碼這個采樣結果,從而得到深度值和法向。該函數為DecodeDepthNormal,定義如下:
DecodeDepthNormal的第壹個參數是深度+法線紋理的采樣結果,這是對單位深度和法向信息進行編碼的結果。其xy分量存儲視圖空間中的法線信息,深度信息被編碼到zw分量中。通過調用DecodeDepthNormal函數對采樣結果進行解碼後,我們可以獲得解碼後的深度值和法線。該深度值是【O l】範圍內的線性深度值(與存儲在單獨深度紋理中的深度值不同),獲得的法線是視角空間中的法線方向。還可以通過調用DecodeFloatRG和DecodeViewNormalStereo來解碼深度+法線紋理中的深度和法線信息。