reconstructing-position-from-depth-for-fullscreen-quads

http://mquandt.com/blog/2010/06/reconstructing-position-from-depth-for-fullscreen-quads/

Recently I found an issue with an older article of mine that covered this topic, so I pulled it down until I could find the time to understand and fix the issue. After a bit of work I have fixed it and present this refresh.

The key to deferred rendering and other techniques is the ability to use a depth map to store world position. As we know a depth map consists of floating point values (optimally) and so normally we would use those values with our Clip Space coordinates and the Inverse of the ViewProjection matrix to get the position in World Space.

However there is another way to do this, and the big benefit is that it does not require a matrix multiplication to do so.

Presented by Crytek during a presentation on Atmospheric Scattering, this method uses just a Multiply + Add to get the position in World Space from our depth value at that point. To do this we need to get the far view frustum corners, in View Space, and pass them to the shader.


Getting the Corners

This is easily done in XNA through the BoundingFrustum.GetCorners() method. Note that we only need the far corners, so we can take the last 4 in the 8 element array, and we also need these corners in View space, so be sure to transform them by the View matrix before you pass them through.

?
1
2
3
4
5
6
7
8
9
10
private void getFrustumCorners( out Vector3[] corners)
{
corners = new Vector3[4];
Vector3[] temp = CurrentCamera.Frustum.GetCorners();
for ( int i = 0; i < 4; i++)
{
corners[i] = Vector3.Transform(temp[i + 4], CurrentCamera.View);
}
}

Since we are in the CPU/XNA code, be sure to also pass the depth of the far clip plane, and the camera’s World matrix. (Simply invert the View matrix if you do not already have a World matrix)

Writing out Depth

Now for some shader code! First in the depth shader, you need to change how you write out the depth value, instead we take the Linear View Space depth, which means that we need to get the Z value from Position * World * View rather than Position * World * View * Projection.

?
1
2
3
4
5
float4x4 wv = mul( World, View );
float4 posVS = mul( pos, wv );
output.Position = mul( posVS, Projection );
output.Depth = posVS.z;

Then in the pixel shader, take this depth value, negate it and divide it by the Far Clipping plane.

?
1
output.Depth = 1.0f - ( -depth / FarClip );

Now when you want to reconstruct this, we need to find the correct frustum corner inside the vertex shader. (Note: This code only applies if you are rendering a fullscreen quad)

Preparing and Choosing the Right Corner

The best way to do this is to pass along the array index as a 3rd value with your texture coordinates. If you do not want to modify the vertex type, you can also apply some maths to determine the right index.

For brevity I will assume that the index is provided with the texture coordinates.

Take that ray and multiply it by the camera World matrix, to move the ray into World space, then pass it to the pixel shader.

?
1
output.FrustRay = mul( FSQ_GetFrustumRay( tcri.z ), CamWorld );

Getting the Position

Now that you have the ray, simply sample the depth texture for the depth value, and multiply it by the ray. Then take that and add it to the camera position to get the point in world space. (Rather than just a direction)

?
1
2
float depth = 1.0f - tex2D( depthSampler, tc ).x;
float3 pos = CamPosition + depth * frustRay;

Now you have your position!

I will include the technique to do the same thing with arbitrary world geometry when I write an article on Point Lights or Spotlights. At the moment I am working on implementing shadows, and probably won’t touch those topics until I solve some pressing issues.

Final Notes

Here is a code sample for passing the index as a part of the texture coordinate value.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private struct VertexPositionTexCoordRayIndex
{
private Vector3 position;
public Vector3 Position
{
get { return position; }
set { position = value; }
}
private Vector3 texCoordRayIndex;
public Vector3 TexCoordRayIndex
{
get { return texCoordRayIndex; }
set { texCoordRayIndex = value; }
}
public static int SizeInBytes { get { return sizeof ( float ) * 6; } }
public static readonly VertexElement[] VertexElements =
new VertexElement[] {
new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
new VertexElement(0, sizeof ( float )*3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0)
};
public VertexPositionTexCoordRayIndex(Vector3 position, Vector3 texcoordRayindex)
{
this .position = position;
this .texCoordRayIndex = texcoordRayindex;
}
}
?
1
2
3
4
5
VertexPositionTexCoordRayIndex[] verts = new VertexPositionTexCoordRayIndex[4];
verts[0] = new VertexPositionTexCoordRayIndex( new Vector3(-1, 1, 1), new Vector3(0, 0, 0));
verts[1] = new VertexPositionTexCoordRayIndex( new Vector3(1, 1, 1), new Vector3(1, 0, 1));
verts[2] = new VertexPositionTexCoordRayIndex( new Vector3(-1, -1, 1), new Vector3(0, 1, 3));
verts[3] = new VertexPositionTexCoordRayIndex( new Vector3(1, -1, 1), new Vector3(1, 1, 2));

Remember that XNA provides the corners in a different order to just 1,2,3,4, so we can explicitly pass the correct index for the vertex we are working with here, and it will be chosen correctly in the shader.


你可能感兴趣的:(3D数学)