Dave K
Dave K

Reputation: 56

Projection matrix causing inaccuracy in clip-space depth calculation?

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.

https://i.sstatic.net/G4omk.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

What I've Tried

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!

Upvotes: 3

Views: 645

Answers (1)

Goz
Goz

Reputation: 62333

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;

Upvotes: 0

Related Questions