Projection matrix causing inaccuracy in clip-space depth calculation?

3

Currently working with SlimDX's Direct3D 11 bindings and having significant difficulty with my shader, the source of which follows:

/* * * * * * * * *
 * PARAM STRUCTS *
 * * * * * * * * */
struct VSPointIn
{
    float3 pos      :   POSITION;
    float4 color    :   COLOR;
};

struct GSParticleIn
{
    float3 pos      :   POSITION;
    float4 color    :   COLOR;
    float radius    :   RADIUS;
};

struct PSParticleIn
{
    float4 pos      :   SV_Position;
    float2 tex      :   TEXCOORD0;
    float3 eyePos   :   TEXCOORD1;
    float radius    :   TEXCOORD2;
    float4 color    :   COLOR;
};

struct PSParticleOut
{
    float4 color    : SV_Target;
    float depth     : SV_Depth;
};

/* * * * * * * * * * * 
 * CONSTANT BUFFERS  *
 * * * * * * * * * * */
cbuffer MatrixBuffer : register(cb1)
{
    float4x4 InvView;
    float4x4 ModelView;
    float4x4 Projection;
    float4x4 ModelViewProjection;
};

cbuffer ImmutableBuffer
{
    float3 Positions[4] =
    {
        float3(-1, 1, 0),
        float3(1, 1, 0),
        float3(-1, -1, 0),
        float3(1, -1, 0)
    };
    float2 Texcoords[4] =
    {
        float2(0, 0),
        float2(1, 0),
        float2(0, 1),
        float2(1, 1)
    };
    float3 LightDirection = float3(-0.5, -0.5, -2.0);
}

/* * * * * * * * * 
 * STATE OBJECTS *
 * * * * * * * * */

BlendState AdditiveBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = TRUE;
    SrcBlend = SRC_ALPHA;
    DestBlend = ONE;
    BlendOp = ADD;
    SrcBlendAlpha = ZERO;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;
};

BlendState NoBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = FALSE;
};

DepthStencilState EnableDepth
{
    DepthEnable = TRUE;
    DepthWriteMask = ALL;
    DepthFunc = LESS_EQUAL;
};

DepthStencilState DisableDepth
{
    DepthEnable = FALSE;
    DepthWriteMask = ZERO;
};

/* * * * * * * * * * * 
 *  SHADER FUNCTIONS *
 * * * * * * * * * * */
GSParticleIn VSParticleMain(VSPointIn input)
{
    GSParticleIn output;

    output.pos = input.pos;
    output.color = input.color;
    output.radius = 5.0;

    return output;
}

[maxvertexcount(4)]
void GSParticleMain(point GSParticleIn input[1], inout TriangleStream<PSParticleIn> SpriteStream)
{
    PSParticleIn output = (PSParticleIn)0;

    [unroll]
    for(int i = 0; i < 4; i++)
    {
        float3 position = Positions[i] * input[0].radius;
        position = mul(position, (float3x3)InvView) + input[0].pos;
        output.pos = mul(float4(position, 1.0), ModelViewProjection);
        output.eyePos = mul(float4(position, 1.0), ModelView).xyz;

        output.color = input[0].color;
        output.tex = Texcoords[i];
        output.radius = input[0].radius;

        SpriteStream.Append(output);
    }
    SpriteStream.RestartStrip();
}

PSParticleOut PSParticleMain(PSParticleIn input)
{
    PSParticleOut output;

    float3 norm;
    norm.xy = (input.tex * 2.0) - 1.0;

    float sqrad = dot(norm.xy, norm.xy);
    if(sqrad > 1.0) discard;
    norm.z = -sqrt(1.0 - sqrad);

    float4 pixelpos = float4(input.eyePos + (norm * input.radius), 1.0);
    float4 clspace = mul(pixelpos, Projection);
    float diffuse = max(0.0, dot(norm, LightDirection));

    output.depth = clspace.z / clspace.w;

    output.color = diffuse * input.color;

    return output;
}
/* * * * * * * * * * * * * *
 * TECHNIQUE DECLARATIONS  *
 * * * * * * * * * * * * * */
technique11 RenderParticles
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, VSParticleMain()));
        SetGeometryShader(CompileShader(gs_5_0, GSParticleMain()));
        SetPixelShader(CompileShader(ps_5_0, PSParticleMain()));

        SetBlendState(NoBlending, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
        SetDepthStencilState(EnableDepth, 0);
    }
}

The intent of the shader is to operate on a point list of colored vertices, expanding each vertex into a billboarded "impostor" sphere using the geometry shader and then writing the final scene to the render target while writing the adjusted depth (the eye-space depth offset by the "sphere" normal) of each pixel to the depth buffer. Currently, this configuration does render the scene correctly as in the following image.

http://i.imgur.com/2SQ6228.png

However, the depth buffer is never written to and when inspected in the graphics debugger, remains solid white (1.0). Since I believe the fault in my code may lie in the way the matrices are constructed or passed to the shader, the following represents the contents of my matrix constant buffer during a sample draw call, for reference:

Inverse View
0   [0x00000000-0x0000000f] |   {      +0.9993909  +6.102026e-009     +0.03489951  +2.132527e-013}
1   [0x00000010-0x0000001f] |   { +7.1054282e-015              +1 -1.7484578e-007  +2.348344e-012}
2   [0x00000020-0x0000002f] |   {    -0.034899514 +1.7473927e-007     +0.99939084      -200.00002}
3   [0x00000030-0x0000003f] |   {              +0              +0              +0              +1}

Model View
4   [0x00000040-0x0000004f] |   {     +0.99939078 +7.1054269e-015     -0.03489951      -6.9799023}
5   [0x00000050-0x0000005f] |   { +6.1020251e-009     +0.99999994 +1.7473926e-007 +3.4947851e-005}
6   [0x00000060-0x0000006f] |   {    +0.034899514 -1.7484578e-007     +0.99939084      +199.87817}
7   [0x00000070-0x0000007f] |   {              +0              +0              +0              +1}

Projection
8   [0x00000080-0x0000008f] |   {      +2.0606079              +0              +0              +0}
9   [0x00000090-0x0000009f] |   {              +0      +2.7474773              +0              +0}
10  [0x000000a0-0x000000af] |   {              +0              +0         +1.0001        -0.10001}
11  [0x000000b0-0x000000bf] |   {              +0              +0              +1              +0}

Model View Projection
12  [0x000000c0-0x000000cf] |   {      +2.0593526 +1.4641498e-014    -0.071914211      -14.382842}
13  [0x000000d0-0x000000df] |   { +1.6765176e-008      +2.7474771 +4.8009213e-007 +9.6018426e-005}
14  [0x000000e0-0x000000ef] |   {    +0.034903005 -1.7486327e-007      +0.9994908      +199.79816}
15  [0x000000f0-0x000000ff] |   {    +0.034899514 -1.7484578e-007     +0.99939084      +199.87817}

What I Know

  • By using Visual Studio 2012's graphics debugger, I am able to confirm that the depth buffer is bound correctly and may be drawn to correctly by the pixel shader. I confirmed this by simply assigning output.depth to some random float in the range [0, 1].

  • My view matrix is generated by the SlimDX.Matrix.LookAtLH() function in the following snippet:

    private Matrix CreateView()
    {
        Matrix rotMatrix;
        var up = Vector3.UnitY;
        var look = LookTarget;
        var rot = Rotation * ((float)Math.PI / 180.0f);
    
        Matrix.RotationYawPitchRoll(rot.Y, rot.X, rot.Z, out rotMatrix);
        Vector3.TransformCoordinate(ref look, ref rotMatrix, out look);
        Vector3.TransformCoordinate(ref up, ref rotMatrix, out up);
        return Matrix.LookAtLH(Position, Position + look, up);
    }
    
  • My projection matrix is generated by SlimDX.Matrix.PerspectiveFovLH() in the following snippet:

    public Camera(Viewport viewport)
    {
        var aspect = viewport.Width / viewport.Height;
        var fov = 40.0f * ((float)Math.PI / 180.0f);
        projectionMatrix = Matrix.PerspectiveFovLH(fov, aspect, 0.1f, 1000.0f);
    }
    
  • My model matrix is simply the identity matrix and is omitted wherever it would appear.

  • Using the graphical debugger I was able to step though the pixel shader to the point where the clip space depth is calculated. Immediately before the shader attempts to divide clipspace.z by clipspace.w, my shader locals look like this:

    http://i.imgur.com/XOGniyd.png

    This was for some arbitrarily selected pixel though every pixel yields similar results for the clspace vector. As you can see, clspace.z and clspace.w are always so similar in magnitude that the division always results in ~1.

What I've Tried

  • Transposing the matrices before uploading them to the device. Did nothing but cause the distortion I would expect to be the result of my projection being inverted, which suggested to me that pre-transposing the matrices was unnecessary with SlimDX's effect framework. (This suspicion was later confirmed, SlimDX's effect framework automatically transposes matrices set as effect variables.)

  • Transposing the matrices implicitly by adding the column/row-major matrix packing flags when compiling my effect. Reversing the multiplication order of vectors and matrices wherever they appeared in the shader. These were misguided attempts at addressing what I thought was a column/row-major issue.

  • Using right-handed projection and view matrices as opposed to left-handed ones. No effect but the expected inversion of axes.

  • Using a number of different near and far projection parameters. They had the expected effect on the near and far clipping distance though did nothing to solve the depth issue.

I've really exhausted every available resource in trying to track down whatever is causing this issue. I've run through just about every possible permutation of matrices, operand order and state settings that I can fathom and referred to countless MSDN articles and SO questions but nothing seems to apply. Greatly appreciate your help with this issue!

c#
directx
direct3d
hlsl
slimdx
asked on Stack Overflow May 14, 2013 by Dave K • edited May 14, 2013 by Dave K

1 Answer

0

Your depth buffer always has the same value written into it because you have your projection matrix wrong. The value that actually gets written is the w-value so you need to juggle your matrix around a bit.

The DirectX documentation gives a good explanation of the perspective transform.

Basically your matrix should be as follows

W, 0, 0, 0
0, H, 0, 0
0, 0, Q, 1
0, 0, P, 0

Where

W = 1.0f / tanf( fovHoriz / 2 );
H = 1.0f / tanf( fovVert  / 2 );
Q = zFar / (zFar - zNear);
P = -Q * zNear;
answered on Stack Overflow May 20, 2013 by Goz • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0