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

6 篇文章 0 订阅

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

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

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

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

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

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

// Shaders渲染线性的深度  
void DepthVS(   in float4 in_vPositionOS    : POSITION,  
                out float4 out_vPositionCS  : POSITION,  
                out float  out_fDepthVS     : TEXCOORD0    )  
{      
    // Figure out the position of the vertex in  
    // view space and clip space  
    float4x4 matWorldView = mul(g_matWorld, g_matView);  
    float4 vPositionVS = mul(in_vPositionOS, matWorldView);  
    out_vPositionCS = mul(vPositionVS, g_matProj);  
    out_fDepthVS = vPositionVS.z;  
}  
   
float4 DepthPS(in float in_fDepthVS : TEXCOORD0) : COLOR0  
{  
    // 取反并除以与远平面的距离,这样深度值的范围就在【0,1】之间   
    // 这是在右手坐标系下做的,左手坐标系深度值不需要取反  
    float fDepth = -in_fDepthVS/g_fFarClip;  
    return float4(fDepth, 1.0f, 1.0f, 1.0f);  
}  
   
// Shaders 延时渲染重建位置信息  
// Vertex shader for rendering a full-screen quad  
void QuadVS (  in float3 in_vPositionOS              : POSITION,  
               in float3 in_vTexCoordAndCornerIndex  : TEXCOORD0,  
               out float4 out_vPositionCS            : POSITION,  
               out float2 out_vTexCoord              : TEXCOORD0,  
               out float3 out_vFrustumCornerVS               : TEXCOORD1    )  
{  
  
/ /偏移0.5个像素的位置使得纹理的像素和屏幕的像素对齐。XNA和D3D9都必须这样做  
        out_vPositionCS.x = in_vPositionOS.x - (1.0f/g_vOcclusionTextureSize.x);  
        out_vPositionCS.y = in_vPositionOS.y + (1.0f/g_vOcclusionTextureSize.y);  
        out_vPositionCS.z = in_vPositionOS.z;  
        out_vPositionCS.w = 1.0f;  
   
        // 利用了纹理坐标和平截头体的角在视图空间的位置。  
        //  
        out_vTexCoord = in_vTexCoordAndCornerIndex.xy;  
        out_vFrustumCornerVS = g_vFrustumCornersVS[in_vTexCoordAndCornerIndex.z];  
}  
   
// PS重建位置信息  
float3 VSPositionFromDepth(float2 vTexCoord, float3 vFrustumRayVS)  
{  
        float fPixelDepth = tex2D(DepthSampler, vTexCoord).r;  
        return fPixelDepth * vFrustumRayVS;  
}  
   

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

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

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

Matrix viewProjMatrix = viewMatrix * projMatrix;  
BoundingFrustum frustum = new BoundingFrustum(viewProjMatrix);  
frustum.GetCorners(frustumCornersWS);  
Vector3.Transform(frustumCornersWS, ref viewMatrix, frustumCornersVS);  
for (int i = 0; i < 4; i++)  
farFrustumCornersVS[i] = frustumCornersVS[i + 4]; 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值