Picking up where I left off here…
As I mentioned, you can also reconstruct a world-space position using the frustum ray technique. The first step is that you need your frustum corners to be rotated so that they match the current orientation of your camera. You can do this by transforming the frustum corners by a “camera world matrix”, which is a matrix representing the camera’s position and orientation in world-space. If you don’t have this available you can just invert your view matrix. I’ll demonstrate doing it right in the vertex shader for the sake of simplicity, but you’d probably want to do it ahead of time in your application code.
// 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_vFrustumCornerWS : TEXCOORD1 )
{
// Offset the position by half a pixel to correctly
// align texels to pixels. Only necessary for D3D9 or XNA
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;
// Pass along the texture coordinate and the position
// of the frustum corner in world-space. This frustum corner
// position is interpolated so that the pixel shader always
// has a ray from camera->far-clip plane
out_vTexCoord = in_vTexCoordAndCornerIndex.xy;
float3 vFrustumCornerVS = g_vFrustumCornersVS[in_vTexCoordAndCornerIndex.z];
out_vFrustumCornerWS = mul(vFrustumCornerVS, g_matCameraWorld);
}
So what we’ve done here is we’ve rotated (not translated, since vFrusumCornerVS is only a float3) the view-space frustum corner so that it’s now matches the camera’s orientation. However it’s still centered around <0,0,0> and not the camera’s world-space position, so when we reconstruct position we’ll also add the camera’s world-space position:
// Pixel shader function for reconstructing world-space position
float3 WSPositionFromDepth(float2 vTexCoord, float3 vFrustumRayWS)
{
float fPixelDepth = tex2D(DepthSampler, vTexCoord).r;
return g_vCameraPosWS + fPixelDepth * vFrustumRayWS;
}
And there it is. Easy peasy, lemon squeezy.
The other bit I hinted at was using this same technique with arbitray geometry, for example the bounding volumes for a local light source. For this we once again need a ray that points from the camera position through the pixel position to the far-clip plane. We can do this in the pixel shader by using the view-space position of the pixel.
void VSBoundingVolume( in float3 in_vPositionOS : POSITION,
out float4 out_vPositionCS : POSITION,
out float3 out_vPositionVS : TEXCOORD0 )
{
out_vPositionCS = mul(in_vPositionOS, g_matWorldViewProj);
// Pass along the view-space vertex position to the pixel shader
out_vPositionVS = mul(in_vPositionOS, g_matWorldView);
}
Then in our pixel shader, we calculate the ray and reconstruct position like this:
float3 VSPositionFromDepth(float2 vTexCoord, float3 vPositionVS)
{
// Calculate the frustum ray using the view-space position.
// g_fFarCip is the distance to the camera's far clipping plane.
// Negating the Z component only necessary for right-handed coordinates
float3 vFrustumRayVS = vPositionVS.xyz * (g_fFarClip/-vPositionVS.z);
return tex2D(DepthSampler, vTexCoord).x * vFrustumRayVS;
}
So there you go, I did your homework for you. Now stop beating me up in the schoolyard!
EDIT: Fixed the code and explanation so that it actually works now! Big thanks to Bill and Josh for pointing out the mistake.
UPDATE: More position from depth goodness here


