全面認識Depth - 這裡有關於Depth的一切

Depth的由來

從PerspectiveProjection 說起

所謂的PerspectiveProjection 其實就是將頂點從view 坐標系下,轉換到NDC下

這裡面包含了兩個步驟,將view 坐標系下的頂點乘以透視矩陣,轉換到Clip 坐標系,得到Clip坐標,

然後統一除以w,得到NDC 坐標。

透視矩陣就不推導了,

這裡再看下Zn和Ze之間的關係

其中n是近裁面,f是遠裁面,r是0.5 *width, t = .5 * height。其中

可以得出

其中A = -(f+n)/(f-n) , B = -2fn/(f-n).兩者的關係可以用下圖來表示

可以看出有下面幾點

  1. 兩者是非線性的關係。曲線是倒數的關係。(y=1/x)

  2. 根據曲線的特點可以知道在近裁面附近的Depth變化非常的明顯,越接近遠裁面,Depth變化越小,精度變差,這時候就會出現惱人的Z-Fighting

  3. 要避免Z-Fighting,n和f之間的距離就要盡量小,盡量將n大一些。

  4. 這樣的好處雖然遠的地方可能會有Z-fighting,但是近處的地方有了更好的深度精度

可能這還不是很清楚,後面還有更精確的數學推導。

viewportntransformation

viewport transformation都知道,就是將ndc坐標map到屏幕空間,那麼深度呢?從[-1,1]映射到[0,1],(OpenGL中)。0是最近,1是最遠. 默認Dw的值在[0,1]就是可以渲的,當然,也可以用 glDepthRange可以用來指定Zw的取值範圍。

viewport transformation 的計算過程如下

將之前計算的Zndc的式子代進來,得到

Zdepth = (We / Ze) * f*n/(f-n) + 0.5 *n(f+n)/(f-n) + 0.5

在存儲到depth buffder 的時候,需要將Zw乘以一個係數s = (2^n -1) ,其中n是Depth buffer的位數。

所以最終Depthbuff中存儲的是

Zw = s * [ (we / ze) *nf*n/(f-n) + 0.5 * (f+n)/(f-n) + 0.5 ]

下面推導一下關於Depth精度的問題。

將上面的式子進行變換,吧Ze放到左邊

Ze / we = f*n/(f-n) / ((zw / s) - 0.5 * (f+n)/(f-n) - 0.5)

= f * n /n((Zw / s) * (f-n) -n0.5 * (f+n) - 0.5 * (f-n))

= f * n /n((Zw / s) * (f-n) - f)n

假設是一個16位的深度緩衝,n = 0.01,nf = 1000,n那麼s = 65535

對於從Depth Buff采出來的值

Zw = 0 ,則 Ze/We = -nn= -0.01

Zw = s = 65535,則Ze/We= -f =n-1000

接下來去Zw = 1和s-1看看

Zw = 1 => Ze /nWe = -0.01000015

Zw = s-1 =>Zen/ We = -395.90054

仔細觀察上面的數據,發現了一個驚天秘密 –neye坐標系下的深度Ze,從-395.9到-1000在Depth buffder中所對應的範圍是[65535, 65536],這是相當恐怖的,導致的結果就是Z-Fighting,下面就是在Fighting。

對於深度造成的誤差,還可以參考這一篇Learningnto Love your Z中的例子。

由深度緩衝重建view pos

知道一個點的深度還有相機的一些參數,重新算出物體的坐標(不管是世界坐標還是view坐標系下的坐標),微軟的kinect利用的就是這個原理,進行三維重建的。

在Deferred shading還有很多後期效果中,我們都需要一個像素點精確的位置來給這個像素著色。

如上圖所示,已知Camera的pos,view Dir,只需要知道surface上的點到Camera的直線距離t就可以求出了: Peyen= Pcam + t*Dview。

現在問題是t怎麼來。

一種處理方法是將在GBuffer Pass中將t存放在一個rt里,具體的流程如下

1. n在GBuffer pass,計算camera到surface的距離,存到rt裡面。

2. n在Light Pass的VS中,計算vertex到camera位置的View Ray向量。

3. n在PS中,將View Ray 單位化,得到Dview。

4. n採樣rt獲取Camera到Surface Pos的距離t

5. nPeye = Pcam + t*Dview。

簡化的Shader如下

// G-Buffer vertex shadernn// Calculate view space position of the vertex and pass it to the pixel shadernnoutput.PositionVS = mul(input.PositionOS, WorldViewMatrix).xyz;n

// G-Buffer pixel shadernn// Calculate the length of the view space position to get the distance from camera->surfacennoutput.Distance.x = length(input.PositionVS);n

// Light vertex shadernn#if PointLight || SpotLightnn// Calculate the world space position for a light volumennfloat3 positionWS = mul(input.PositionOS, WorldMatrix);nn#elif DirectionalLightnn// Calculate the world space position for a full-screen quad (assume input vertex coordinates are in [-1,1] post-projection space)nnfloat3 positionWS = mul(input.PositionOS, InvViewProjMatrix);nn#endifnn// Calculate the view raynnoutput.ViewRay = positionWS - CameraPositionWS;n

// Light Pixel shadernn// Normalize the view ray, and apply the original distance to reconstruct positionnnfloat3 viewRay = normalize(input.ViewRay);nnfloat viewDistance = DistanceTexture.Sample(PointSampler, texCoord);nnfloat3 positionWS = CameraPositionWS + viewRay * viewDistance;n

但是還有優化的空間,上面提到的這種做法的缺點是浪費了顯存和帶寬,而Depth 就在BackBuffer,如果能夠從Depth中得到view pos的話,優化又進了一步。

由於硬體中的Depth Buffer是除以過w的,所以

第一步要做的就是將Depth buffer中的值恢復到view 坐標系下的Depth,就叫Ze,

由上面推導的結果

Zdepth = (We / Ze) * f*n/(f-n) + 0.5 * (f+n)/(f-n) + 0.5n

通過f和n就可以推算出Ze了。

另一種方法是利用透視矩陣M,根據透視變換

其中Zndc =2 * depth – 1.原理是一樣的。

沒線性化之後的深度渲染出來是這樣的(獅子的鼻子),稍微遠一些的地方深度值都很接近1了,所以很白,只有很近的地方才有灰色的部分。

將線性化之後的depth渲染出來

不是一片白了。

知道深度之後,通過相似三角形就可以求出

接下來就是求View ray,這個放在ps中做,

ViewRay = float3(positionVS.xy / positionVS.z, 1.0f);n

如果要將view坐標系的坐標轉換到世界坐標系,

還有一種簡單暴力的處理方法

vec3 DepthToPos(float depth, vec2 texCoord)nn{nnvec4 ndcspace = vec4(texCoord.x * 2.0 - 1.0, texCoord.y * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);nnvec4 temp = inverse(MatView) * inverse(MatProj) * ndcspace;nnreturn temp.xyz / temp.w;nn}n

直接用矩陣來處理,性能稍微會差一些(待考究)。

參考

12 The Depth Buffer

Learning to Love your Z-buffer.

OpenGL viewport transformation matrix - The Code Crate

詳解MVP矩陣之齊次坐標和ModelMatrix

詳解MVP矩陣之齊次坐標和ViewMatrix

詳解MVP矩陣之齊次坐標和ProjectionMatrix


推薦閱讀:

請教各位在pc上使用webgl以及opengl渲染yuv數據的性能問題,有何改進方向?
震驚!時間之神又給了這個古老的API+了0.1
如何實現魚眼效果?
在 OpenGL 中應用查找表(LUT)技術時,如何避免紋理數據自動歸一化導致的圖像異常?
不同GLSL版本之間的區別?

TAG:计算机图形学 | 游戏开发 | OpenGL |