投影矩阵导致剪裁空间深度计算不准确?

3

目前正在使用SlimDX的Direct3D 11绑定,但是我的着色器遇到了很大的困难。以下是我的着色器源代码:

/* * * * * * * * *
 * 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);
    }
}

着色器的意图是操作一个带有颜色的顶点列表,使用几何着色器将每个顶点扩展为一个“冒牌”球体,并将最终场景写入渲染目标,同时将每个像素的调整深度(通过“球体”法线偏移的眼空间深度)写入深度缓冲区。目前,这种配置确实可以正确地呈现场景,如下图所示。

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

然而,深度缓冲区从未被写入,并且在图形调试器中检查时保持纯白色(1.0)。由于我认为代码中的错误可能在于矩阵的构造方式或传递到着色器的方式,因此以下是参考样本绘制调用期间我的矩阵常量缓冲区的内容:
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}

我知道什么

  • 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.

我尝试过的方法

  • 在上传矩阵到设备之前对它们进行转置。这只会导致变形,就像我预期的投影被倒置一样,这表明在SlimDX的效果框架中预先转置矩阵是不必要的。(后来证实了我的怀疑,SlimDX的效果框架会自动转置设置为效果变量的矩阵。)

  • 通过在编译效果时添加列/行主矩阵打包标志来隐式地转置矩阵。在着色器中出现的向量和矩阵的乘法顺序相反。这些都是我认为是列/行主问题的错误尝试。

  • 使用右手投影和视图矩阵而不是左手的。没有任何效果,只有轴的预期反转。

  • 使用许多不同的近和远投影参数。它们对近和远裁剪距离产生了预期的影响,但对深度问题没有任何作用。

我已经竭尽所能地尝试了每一个可用的资源,以追踪造成这个问题的原因。我已经运行了几乎我能想象到的所有矩阵、操作顺序和状态设置的排列组合,并参考了无数的MSDN文章和SO问题,但似乎没有任何适用的解决方案。非常感谢您对这个问题的帮助!


我无法在原始问题中添加更多链接,但是我也尝试了将我的投影矩阵除以该矩阵的(3,4)分量,如MSDN文章“W友好的投影矩阵”所述,但没有效果。 - Dave K
增加半径或减小观察者到球体的距离是否有帮助? - gareththegeek
我确信深度缓冲区的数值已经成功写入。请看图像右侧相交的红色和紫色球体。如果没有有效的深度数值,相交处将会出现错误。您还遇到其他问题吗? - Nico Schertler
1个回答

0

你的深度缓冲区始终具有相同的值,因为你的投影矩阵有误。实际写入的值是w值,因此你需要对矩阵进行一些调整。

DirectX文档对透视变换给出了很好的解释。

基本上,你的矩阵应该如下所示:

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

在哪里

W = 1.0f / tanf( fovHoriz / 2 );
H = 1.0f / tanf( fovVert  / 2 );
Q = zFar / (zFar - zNear);
P = -Q * zNear;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接