[置顶] Reconstructing Position From Depth(从深度缓冲里重建位置信息)

 

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

   首先我必须让大家知道,我们现在讨论的是什么问题?我们研究的是延时渲染中一个非常有用的应用:从一个深度值中还原前一帧渲染像素的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来确定那个顶点属于哪个角。另一种方法你可以把角的位置信息直接存在顶点纹理坐标里面。

 

 

 

你可能感兴趣的:(vector,float,Matrix,跨平台,shader,Constants)