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配合)生成的阴影:

由此进一步证明了重建结果的正确性。

原文地址:https://www.cnblogs.com/wantnon/p/6979903.html