从深度缓冲里重建位置信息



(从深度缓冲里重建位置信息)

   首先我必须让大家知道,我们现在讨论的是什么问题?我们研究的是延时渲染中一个非常有用的应用:从一个深度值中还原前一帧渲染像素的3D位置信息(可以是在视图空间也可以是在世界空间的)。在实践中,其实并不复杂。对任意像素进行渲染的时候,你本来就知道它2D的位置 ,采样一个深度值中便可以得到它完整的3D位置信息。不过重建信息时候仍然很容易陷入困境中,虽然你有很多种方式可以达到目的,但是很多初学者并不是很擅长Debug自己的着色器。

     方法一:把投影矩阵的z/w存起来,再和x / w和y / w进行组合运算,通过投影矩阵的逆的进行变换,最后除以w。以下为HLSL代码:

[cpp]   view plain copy
  1. Depth pass:  
  2. //顶点着色  
  3. output.vPositionCS = mul(input.vPositionOS, g_matWorldViewProj);  
  4. output.vDepthCS.xy = output.vPositionCS.zw;  
  5.    
  6. //像素着色(输出 z/w)  
  7. return input.vDepthCS.x / input.vDepthVS.y;  
  8.    
  9. 获取位置信息:  
  10. // 这个方法在延时的像素着色器把深度信息转换到视图空间去  
  11. //vTexCoord 是全屏纹理的坐标,x=0是屏幕的左边,Y=0是屏幕的上面  
  12. float3 VSPositionFromDepth(float2 vTexCoord)  
  13. {  
  14.     // 获取深度信息  
  15.     float z = tex2D(DepthSampler, vTexCoord);    
  16. // 从视口坐标中获取 x/w 和 y/w  
  17. float x = vTexCoord.x * 2 - 1;  
  18.     float y = (1 - vTexCoord.y) * 2 - 1;  
  19.     float4 vProjectedPos = float4(x, y, z, 1.0f);  
  20.     // 通过转置的投影矩阵进行转换到视图空间  
  21.     float4 vPositionVS = mul(vProjectedPos, g_matInvProjection);    
  22.     // Divide by w to get the view-space position  
  23.     return vPositionVS.xyz / vPositionVS.w;    
  24. }  
 

 

对许多人来说,这种是首选的方法,因为它使用的是硬件深度缓冲工作。它有些东西看起来很自然很简单:我们通过投影得到深度值,我们又通过逆投影矩阵得到的位置信息。如果我们无法访问硬件深度缓冲怎么办?如果你在PC和D3D9上开发,像采样一张纹理一样采样深度缓冲是不可能的,除非你通过驱动Hacks钩子的方法(译者:打破常规的作法,D3D是不允许的)。如果你使用的是XNA,所有的框架跨平台兼容PC和360完成这件事情是不可能的。在这样情况下,我们可以直接使用顶点和像素着色器渲染出一张深度缓冲。这是一个好的方法么?z / w是非线性的,而大多数精度需要非常接近近裁剪平面。

        一种不同的方法是,将归一化的视图空间的Z作为我们的深度。因为它是视图空间的是线性的,这就意味着我们可以得到一致的精度分布,这也意味着我们不必操心使用投影或投影的逆去重建位置。相反,我们可以采取类似于CryTek的方法,把这个深度值与一条从摄像机指向远平面的射线相乘。HLSL代码如下:

 

 
 
[c-sharp] view plain copy
  1. // Shaders渲染线性的深度  
  2. void DepthVS(   in float4 in_vPositionOS    : POSITION,  
  3.                 out float4 out_vPositionCS  : POSITION,  
  4.                 out float  out_fDepthVS     : TEXCOORD0    )  
  5. {      
  6.     // Figure out the position of the vertex in  
  7.     // view space and clip space  
  8.     float4x4 matWorldView = mul(g_matWorld, g_matView);  
  9.     float4 vPositionVS = mul(in_vPositionOS, matWorldView);  
  10.     out_vPositionCS = mul(vPositionVS, g_matProj);  
  11.     out_fDepthVS = vPositionVS.z;  
  12. }  
  13.    
  14. float4 DepthPS(in float in_fDepthVS : TEXCOORD0) : COLOR0  
  15. {  
  16.     // 取反并除以与远平面的距离,这样深度值的范围就在【0,1】之间   
  17.     // 这是在右手坐标系下做的,左手坐标系深度值不需要取反  
  18.     float fDepth = -in_fDepthVS/g_fFarClip;  
  19.     return float4(fDepth, 1.0f, 1.0f, 1.0f);  
  20. }  
  21.    
  22. // Shaders 延时渲染重建位置信息  
  23. // Vertex shader for rendering a full-screen quad  
  24. void QuadVS (  in float3 in_vPositionOS              : POSITION,  
  25.                in float3 in_vTexCoordAndCornerIndex  : TEXCOORD0,  
  26.                out float4 out_vPositionCS            : POSITION,  
  27.                out float2 out_vTexCoord              : TEXCOORD0,  
  28.                out float3 out_vFrustumCornerVS               : TEXCOORD1    )  
  29. {  
  30.   
  31. / /偏移0.5个像素的位置使得纹理的像素和屏幕的像素对齐。XNA和D3D9都必须这样做  
  32.         out_vPositionCS.x = in_vPositionOS.x - (1.0f/g_vOcclusionTextureSize.x);  
  33.         out_vPositionCS.y = in_vPositionOS.y + (1.0f/g_vOcclusionTextureSize.y);  
  34.         out_vPositionCS.z = in_vPositionOS.z;  
  35.         out_vPositionCS.w = 1.0f;  
  36.    
  37.         // 利用了纹理坐标和平截头体的角在视图空间的位置。  
  38.         //  
  39.         out_vTexCoord = in_vTexCoordAndCornerIndex.xy;  
  40.         out_vFrustumCornerVS = g_vFrustumCornersVS[in_vTexCoordAndCornerIndex.z];  
  41. }  
  42.    
  43. // PS重建位置信息  
  44. float3 VSPositionFromDepth(float2 vTexCoord, float3 vFrustumRayVS)  
  45. {  
  46.         float fPixelDepth = tex2D(DepthSampler, vTexCoord).r;  
  47.         return fPixelDepth * vFrustumRayVS;  
  48. }  
  49.    

就像你看到的一样,使用线性的深度信息重建出来的位置相当的不错。我们只需要使用一个简单的乘法,就替代了4次的矩阵运算 和一次与投影的除法运算。如果你好奇得到我们所使用平截头体的角位置的方法,这个网站有介绍。

http://www.lighthouse3d.com/opengl/viewfrustum/index.php?defvf

如果你正在使用的是XNA,将会更方便,有一个super-convient BoundingFrustum,里面可以直接得到。我获取位置的方法类似于(XNA):

 
 
[c-sharp] view plain copy
  1. Matrix viewProjMatrix = viewMatrix * projMatrix;  
  2. BoundingFrustum frustum = new BoundingFrustum(viewProjMatrix);  
  3. frustum.GetCorners(frustumCornersWS);  
  4. Vector3.Transform(frustumCornersWS, ref viewMatrix, frustumCornersVS);  
  5. for (int i = 0; i < 4; i++)  
  6. farFrustumCornersVS[i] = frustumCornersVS[i + 4];  

数组farFrustumCornersVS作为Shader Constants传给顶点着色器。那么你在正方形顶点上需要有一个index来确定那个顶点属于哪个角。另一种方法你可以把角的位置信息直接存在顶点纹理坐标里面。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值