unity, 在image effect shader中用_CameraDepthTexture重建世界坐標


--------------更新

更簡單的方法:

     //depth: raw depth, nonlinear, 0 at near plane, 1 at far plan

           float4 screenUVwithDepth=float4(screenUV,depth,1);//now x,y,z are in [0,1]
                ndcPos=screenUVwithDepth*2-1;//now x,y,z are in [-1,1] and w=1
                float4 worldPos=mul(clipToWorld,ndcPos);

     worldPos/=worldPos.w;//important

--------------舊帖

我用於渲染_CameraDepthTexture的相機是一個透視相機。正交的情況沒試,估計差不多。

unity的image effect的機制我們大致都了解:它是畫了一個覆蓋全屏的quad(具體尺寸和位置未知)。

要注意的是image effect使用的投影矩陣並非相機的投影矩陣,而是使用了一個正交投影矩陣(具體形式未知),這一點我卡了好久才意識到,不過一想也很正常合理,畢竟只是想畫個全屏quad,直接push個正交矩陣(即切換到2d模式)去畫就好了。

雖然image effect使用的投影矩陣和相機不一致,但兩者之間的聯系是投影到2d屏幕的結果是相同的,這是關鍵。

下面開始重建:

對於image effect下的當前像素p,可以獲得其屏幕坐標sPos_p=(screenX_p,screenY_p,0),這也是p在相機下的屏幕坐標,由於_CameraDepthTexture恰好鋪滿相機屏幕,所以p在_CameraDepthTexture上的紋理坐標sUV_p=sPos_p.xy/screenSize,即p點處深度值為

depth=_CameraDepthTexture(sUV_p)

設p點處深度值為depth的點為p',則p’在相機空間的z值為

linearDepth=LinearEyeDepth(depth)

如圖,即|QP'|=linearDepth:

現在我們要求的是P'的世界坐標,計算如下:

1,先求P點的世界坐標,分兩步:

(1)求p的歸一化設備空間坐標ndcPos_p=screenToNDC*sPos_p.

(2)求p的世界坐標wPos_p:

  temp=clipToWorldMatrix*ndcPos_p. (clipToWorldMatrix須從外部傳入)

  wPos_p=temp/temp.w

2,設由eyeWorldPos指向P的單位向量為dir,則dir=normalize(wPos_p-eyeWorldPos).

3,distance(eyeWorldPos,P')=|QP'|/cos(theta)=linearDepth/dot(eyeWorldDir,dir)

4,最終P'點世界坐標為wPos_p'=eyeWorldPos+dir*distance(eyePos,P')

重建完成。

說明:

1,eyeWorldPos可在shader中通過內置變量獲取。eyeWorldDir我試過直接用內置變量UNITY_MATRIX_V [2].xyz,結果似乎不對,於是改用了從外部傳入camera.tranform.forward,這樣結果是對的。

2,這里只是為說明原理方便,使用了“image effect和相機兩種渲染模式下屏幕空間坐標相等”這個條件來解題,而實際上若利用“兩種模式下歸一化設備空間坐標相等”來做更簡單些,至少不用把screenSize牽扯進來了。

3,clipToWorldMatrix必須從外部傳入,因為

clipToWorldMatrix=camera.cameraToWorldMatrix*camera.ProjectionMatrix.inverse

其中camera.ProjectionMatrix必須從外部傳入才能獲得正確的值,因為image effect shader內部已經被unity偷糧換柱成正交矩陣了。

4,Projection或unProjection的過程一定不要忘了做透視除法。

5,linearDepth的確切含義是“相機空間中的z坐標”,按此含義,它表示的是前面圖中|QP'|的長度,而非eye到P'的距離。

6,如何驗證上面方法重建出的世界坐標是正確呢?

我采用的方法是把distance(eyeWorldPos,P')/farPlane作為顏色值渲染出來。然后再另搞一個非重建的測試用例,也把distance(eyeWorldPos,worldPos)/farPlane作為顏色值渲染出來(使用完全相同的測試場景,完全相同的相機角度),然后看兩幅圖是否一模一樣。我測試的結果是一樣的。

----補充:

1,如果嫌unity自帶的image effect機制有太多黑箱,比如全屏quad位置和尺寸不詳,所使用的投影矩陣不詳,也可以自己搞custom的image effect機制來替換unity自帶機制:自己用GL指令畫全屏quad,自己push投影矩陣。參考:http://flafla2.github.io/2016/10/01/raymarching.html(這是一篇講在unity中搭建raymarching環境的文章,其中采用了自定義image effect機制的做法)。

2,在網上搜到一些其它重建源碼:

https://github.com/zezba9000/UnityMathReference/tree/master/Assets/Shaders/DepthBuffToWorldPos
(這個我仔細測試了一下,效果是正確的,但其DepthBuffToWorldPosDemo.cs中計算clipToWorld的方法有點兒怪,我沒理解)

https://github.com/keijiro/DepthToWorldPos

(這個沒細測,感覺也是正確的)

----補充2

進一步驗證,用重建的世界坐標(與一張shadow map配合)生成的陰影:

由此進一步證明了重建結果的正確性。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM