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, which you can actually do by transposing it (since your view matrix should be orthogonal unless you’re doing something really really weird). 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.
May 5, 2009 at 3:14 pm
[...] Answers to extra credit questions here Posted in DirectX, Programming, XNA. 2 Comments [...]
May 6, 2009 at 1:07 pm
I don’t think your solution for arbitrary geometry works. You alluded to the problems yourself in your previous post.
Interpolationg (xyz / z) per-vertex doesn’t work as it is not a linear operation. You have to do the division in the pixel shader for this to work.
June 20, 2009 at 11:11 pm
Phil was correct. I tried doing the calculation in the vertex shader for my code, and it introduced very nasty visual artifacts. When I moved the calculation to the fragment shader, it produced the correct results.
July 20, 2009 at 9:55 am
Yup, you guys are right. For a while I was trying to figure out why I wasn’t getting artifacts…and then I realized that in code I was calculating the ray in the pixel shader too. Whoops.
Thanks everyone for pointing it out, much appreciated.
August 30, 2009 at 8:20 pm
A heads up to those using the first technique, it expects coordinates in the 0 to 1 range. Which should have been apparent with the texCoord parameter.
My own world space arbitrary implementation:
float3 GetFrustumRay(in float2 screenPosition)
{
float2 sp = sign(screenPosition);
return float3(Camera.FrustumRay.x * sp.x, Camera.FrustumRay.y * sp.y, Camera.MaxDepth);
}
The Camera.FrustumRay is calculated in the application using the following:
Vector2 frustumRay = new Vector2();
frustumRay.Y = (float)Math.Tan(Math.PI / 3.0 / 2.0) * camera.Viewport.MaxDepth;
frustumRay.X = -(frustumRay.Y * camera.Viewport.AspectRatio);
August 30, 2009 at 8:22 pm
Forgot to mention in that last one, you alike the first one multiply view space depth (negated if your not using floating point buffers) then add camera position.