根據(jù)深度信息重建屏幕像素在世界中的坐標

談起深度信息,我第一個反應就是陳嘉棟大大寫的# 神奇的深度圖:復雜的效果缔刹,不復雜的原理振湾,最近看到SSAO技術(shù)里面也會用到深度信息杀迹,去重建屏幕上像素在世界空間下的坐標,閱讀相關(guān)代碼時看到重建這個信息的代碼很簡單:

float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
//自己操作深度的時候押搪,需要注意Reverse_Z的情況
#if defined(UNITY_REVERSED_Z)
    depthTextureValue = 1 - depthTextureValue;
#endif
float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depthTextureValue * 2 - 1, 1);
float4 worldPos = mul(_InverseVPMatrix, ndc);
worldPos /= worldPos.w;

就是采樣深度圖树酪,將得到的數(shù)據(jù)對應到[-1,1]之間(ndc坐標是在這個區(qū)間),然后乘以一個vp的逆矩陣大州,最后除以w就好了续语。那么,這到底怎么來的厦画,這篇博客就來推導下疮茄。

首先對于這個問題,我們有哪些已知信息根暑,這是很重要的×κ裕現(xiàn)在已知屏幕上的像素坐標(用xy或者uv表達都行),深度信息z也是已知的排嫌,vp矩陣已知則其逆矩陣也已知畸裳。到此,我們嘗試推導下能不能重建世界坐標淳地。

已知:\frac{clip_{xyz}}{clip_w} = ndc_{xyz}怖糊,

vp^{-1} * clip_{xyzw} = world_{xyzw}

ndc下的xyz現(xiàn)在已知帅容,那么我們只需要知道clip_w就能知道像素所對應的剪裁空間坐標,那么得到世界空間坐標就易如反掌了伍伤。

現(xiàn)在問題來了并徘,根據(jù)上面的已知條件,似乎沒法知道clip_w是多少啊扰魂。麦乞。。

這里其實我們已知的信息除了上面所說外阅爽,還有一個很重要的信息路幸,就是世界空間坐標下w=1。有了這個以后付翁,我們就能繼續(xù)推導了简肴。

已知:vp^{-1} * clip_{xyzw} = world_{xyzw}world_w = 1

那么:vp^{-1} * (clip_{xyz},clip_w) = world_{xyzw}
=> vp^{-1} * (ndc_{xyz} * clip_{w} ,clip_w) = world_{xyzw}
=> clip_{w} * vp^{-1} * (ndc_{xyz},1) = world_{xyzw} (式子1)

到了式子1這一步百侧,世界空間下w=1的功能就要發(fā)揮出來了砰识。

已知:world_w = 1

那么:clip_{w} * (vp^{-1} * (ndc_{xyz},1)) _w = 1

為什么會這樣呢?clip_{w}是個標量佣渴,vp^{-1} * (ndc_{xyz},1)是個vector4辫狼,有xyzw四個分量,其中w的分量乘以clip_{w}是1辛润,以對應世界空間下world_w = 1膨处,所以有了式子1

所以:clip_{w} = \frac{1}{(vp^{-1} * (ndc_{xyz},1))_w}

現(xiàn)在clip_{w}知道了砂竖,將它代入式子1就可以算出世界坐標了真椿。

最終:world = \frac{vp^{-1} * (ndc_{xyz},1)}{(vp^{-1} * (ndc_{xyz},1)_w}

現(xiàn)在代碼中的為什么要這么寫就應該很清楚了。

2020.11.26更新
上面的方法雖然直觀乎澄,但涉及到矩陣運算突硝,效率上會有些影響。那么有沒有不涉及矩陣運算也能得出世界坐標的方法呢置济?有解恰!具體代碼如下

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(i.uv, _CameraDepthTexture_ST));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);

我們需要一個相機在世界坐標下的位置,深度信息浙于,以及射線信息就能得出世界坐標了护盈。那么有人要問了,深度信息我知道羞酗,可以從深度圖中獲取腐宋,相機信息引擎也提供,那么這個射線信息是什么鬼呢?還有為什么這樣可以得出世界坐標呢脏款?
我們先來解決一個問題,上面這個式子怎么來的裤园。根據(jù)Secrets of CryENGINE 3 Graphics Technology這個PPT來看撤师,我們可以這么來理解。

上面是PPT中的原圖拧揽,下面我自己手繪了一下二維版的剃盾,這樣理解起來就相對容易。

A作為攝像機淤袜,BC為遠剪裁面痒谴,前面那條豎線是近剪裁面(忘記標記名字了= =),我們需要算出F點的位置铡羡,那么容易得到F = A + AFAF為向量积蔚,F即為我們想要計算的世界坐標),現(xiàn)在AF該怎么計算呢烦周?我們知道尽爆,如果將深度限制在01空間中時,AD=1读慎,所以上面代碼在使用深度時使用Linear01Depth將深度轉(zhuǎn)換到01空間下漱贱。并且由相似三角形定理得AG / AD = AF / AE,現(xiàn)在AD=1夭委,那么AF = AG * AE幅狮,AG即為01空間的深度d,那么AF = AE * d≈昃模現(xiàn)在問題轉(zhuǎn)化為AE怎么求崇摄,而這個AE代表著攝像機到屏幕上每個像素的射線。針對屏幕后處理來說蚂且,我們實際上只是在渲染一個Quad配猫,那么我們只需要通過攝像頭上的一些參數(shù),將攝像機屏幕上的四個頂點計算出來杏死,然后利用光柵化的插值功能泵肄,即可得到我們想要的射線AE了。

C#計算四個頂點的代碼為

        cam = GetComponent<Camera>();
        cam.depthTextureMode |= DepthTextureMode.Depth;
        camTrans = cam.transform;

        Matrix4x4 frustumCornors = Matrix4x4.identity;
        float fov = cam.fieldOfView;
        float near = cam.nearClipPlane;
        float far = cam.farClipPlane;
        float aspect = cam.aspect;

        float fovWHalf = fov * 0.5f;

        Vector3 toRight = camTrans.right * near * Mathf.Tan (fovWHalf * Mathf.Deg2Rad) * aspect;
        Vector3 toTop = camTrans.up * near * Mathf.Tan (fovWHalf * Mathf.Deg2Rad);

        Vector3 topLeft = (camTrans.forward * near - toRight + toTop);
        float camScale = topLeft.magnitude * far/near;

        topLeft.Normalize();
        topLeft *= camScale;

        Vector3 topRight = (camTrans.forward * near + toRight + toTop);
        topRight.Normalize();
        topRight *= camScale;

        Vector3 bottomRight = (camTrans.forward * near + toRight - toTop);
        bottomRight.Normalize();
        bottomRight *= camScale;

        Vector3 bottomLeft = (camTrans.forward * near - toRight - toTop);
        bottomLeft.Normalize();
        bottomLeft *= camScale;

        frustumCornors.SetRow(0, bottomLeft);
        frustumCornors.SetRow(1, bottomRight);
        frustumCornors.SetRow(2, topRight);
        frustumCornors.SetRow(3, topLeft);

        mat.SetMatrix("_Ray", frustumCornors);

Shader這邊只要這樣處理就行了

SubShader {
        ZWrite Off 
        ZTest Always 
        Cull Off
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
           
            #include "UnityCG.cginc"
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 ray : TEXCOORD1;
            };
            
            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv,_MainTex);
                o.uvDepth = v.uvDepth;

            
                int index = 0;
                if (v.uv.x < 0.5 && v.uv.y < 0.5){
                    index = 0;
                }else if (v.uv.x > 0.5 && v.uv.y < 0.5){
                    index = 1;
                }else if (v.uv.x > 0.5 && v.uv.y > 0.5){
                    index = 2;
                }else{
                    index = 3;
                }
             

                o.ray = _Ray[index].xyz;
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(i.uv, _CameraDepthTexture_ST));
                float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);

                return fixed4(wp,1.0);
            }
            ENDCG
        }
    }

參考
Unity Shader 深度值重建世界坐標
Unity Shader-深度相關(guān)知識總結(jié)與效果實現(xiàn)(LinearDepth淑翼,Reverse Z腐巢,世界坐標重建,軟粒子玄括,高度霧冯丙,運動模糊,掃描線效果)
How to go from device coordinates back to worldspace in OpenGL (with explanation)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胃惜,隨后出現(xiàn)的幾起案子泞莉,更是在濱河造成了極大的恐慌,老刑警劉巖船殉,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲫趁,死亡現(xiàn)場離奇詭異,居然都是意外死亡利虫,警方通過查閱死者的電腦和手機挨厚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糠惫,“玉大人疫剃,你說我怎么就攤上這事∨鸱恚” “怎么了巢价?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長固阁。 經(jīng)常有香客問我蹄溉,道長,這世上最難降的妖魔是什么您炉? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任柒爵,我火速辦了婚禮,結(jié)果婚禮上赚爵,老公的妹妹穿的比我還像新娘棉胀。我一直安慰自己,他們只是感情好冀膝,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布唁奢。 她就那樣靜靜地躺著,像睡著了一般窝剖。 火紅的嫁衣襯著肌膚如雪麻掸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天赐纱,我揣著相機與錄音脊奋,去河邊找鬼。 笑死疙描,一個胖子當著我的面吹牛诚隙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播起胰,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼久又,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起地消,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤炉峰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脉执,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讲冠,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年适瓦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谱仪。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡玻熙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疯攒,到底是詐尸還是另有隱情嗦随,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布敬尺,位于F島的核電站枚尼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏砂吞。R本人自食惡果不足惜署恍,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜻直。 院中可真熱鬧盯质,春花似錦、人聲如沸概而。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赎瑰。三九已至王悍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間餐曼,已是汗流浹背压储。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留源譬,地道東北人渠脉。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像瓶佳,于是被迫代替她去往敵國和親芋膘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345