從NDC(歸一化的設備坐標)坐標轉換到世界坐標要點
參考資料
How to go from device coordinates back to worldspace http://feepingcreature.github.io/math.html
《Unity Shader入門精要》
前情提要,從運動模糊說起
產生運動模糊效果,一般來說有兩種做法,第一種是將當前幀和下一幀或上一幀等等圖像混合起來作為當前的屏幕圖像,這樣做法可以導致物體運動時會出現多個殘影(因為多個幀混合起來了),可以產生運動模糊效果,但效率不高。
第二種做法是,在Shader里面利用View-Projection矩陣及其逆矩陣獲得當前幀和上一幀的世界坐標,通過這兩個坐標得到當前像素的運動速度及其方向,再根據這個速度,向這個像素速度方向的N個紋素進行取樣並混合,從而產生模糊效果。
而這第二種做法,就是我遇到問題的地方。
總所周知,在Shader里面,可以根據深度圖和當前uv坐標得到當前像素的NDC坐標,那么只要采用View-Projection(視圖-裁剪)的逆矩陣就可以將NDC坐標變換到世界坐標下(正是我們所需要的值),按理說,這樣應該就可以了,但是我在《Unity Shader入門精要》中看到的計算方法卻是下面這樣。
1 fixed4 frag(v2f i) : SV_Target{
2 // 從深度圖中獲得深度
3 float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
4 // 根據深度和當前uv坐標反推當前NDC坐標(注意這個坐標已經經過了齊次除法了)
5 float4 NDCPos = float4(i.uv.x*2-1, i.uv.y*2-1, d*2-1,1);
6 // 根據NDC坐標及View-Projection的逆矩陣,將NDC坐標變換到世界坐標下
7 float4 worldPos = mul(_CurrentViewProjectionInverseMatrix,NDCPos);
8
9 worldPos /= worldPos.w;
....
....
....
}
其中第9行是我最疑惑的內容,按說,你進行屏幕映射要除個w分量進行齊次除法我可以理解,但是你從NDC坐標變換到世界坐標還要除於w是個什么理喔?
百思不得其解之下,翻了下作者的GitHub,嗯,終於找到了答案(感謝作者及評論區的小伙伴!!!)。
在 http://feepingcreature.github.io/math.html 中有詳細的數學證明,下面我給翻譯一下~~
數學推導過程
已知條件
首先考慮從世界坐標是如何變換到NDC坐標下的。
很顯然,首先世界坐標是通過VP矩陣(不用MVP,因為這里已經是世界坐標了,不用在從模型空間變換到世界空劍俠了)變換到了裁剪空間下,數學公式如下:
在裁剪空間下,通過進行齊次除法將坐標變換到NDC坐標中,公式如下:
最后,第三個已知條件是,世界坐標下的w分量在Unity中定義通常都是1,那么就是下面這樣:
分析問題
那么,我們的問題就變成了下面這樣:
已知:
①
②
③
三個公式,給定一個NDC坐標和一個View-Projection矩陣,求得該NDC坐標所對應的世界坐標.
推導
第一步,反轉公式③,如下所示:
④
第二步,根據公式2可得:
⑤
同時,又因為clip=(clipxyz,clipw),所以可以根據公式⑤,可得下面的公式:
⑥
根據公式④和⑥,可得下面的公式:
⑦
下面有個解釋不是很懂~,原文如下:
And note that since matrices are linear transforms, we can pull that clip_w in front of the matrix multiply:
大致意思是說,因為矩陣是線性變化的,所以可以把clipw放到矩陣的前面.(老實說clipw不是標量么,放哪里應該都可以把?)
總之,經過上面原文的解釋,公式⑦變成下面這樣.
⑦
clip的w分量怎么得?
那么,一個比較嚴重的問題就出現了,clip坐標的w分量我們並不知道欸~~~我們已知的只有NDC坐標下的(X,Y,Z)分量(分別可以通過當前像素的uv值和深度算出來).
別急,已知條件不是還有一個沒用么,就下面這個已知條件.
①
已知世界坐標的w分量恆為1,根據公式①、⑦,在只關注運算過程和結果的w分量的情況下,有下面這個公式:
⑧
那么,clip的w分量就經過公式⑧算出來了。
最終結果
那么,已知clipw,根據公式⑧和公式⑦,得到下面的公式⑨。
⑨
總結
根據公式⑨,就可以知道為什么最后我們還要除於w分量了~~~~~因為下面的分母就是w分量啊。。