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

时间:2013-05-14 22:08:47

标签: c# directx direct3d hlsl slimdx

目前使用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}

我所知道的

  • 通过使用Visual Studio 2012的图形调试器,我能够确认深度缓冲区是否正确绑定,并且可以通过像素着色器正确绘制。我通过简单地将output.depth分配给[0,1]范围内的某个随机浮点数来确认这一点。

  • 我的视图矩阵由以下代码段中的SlimDX.Matrix.LookAtLH()函数生成:

    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);
    }
    
  • 我的投影矩阵由SlimDX.Matrix.PerspectiveFovLH()在以下代码段中生成:

    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);
    }
    
  • 我的模型矩阵只是单位矩阵,无论出现在哪里都会被省略。

  • 使用图形化调试器,我可以通过像素着色器逐步到达计算剪辑空间深度的点。在着色器尝试通过clipspace.w划分clipspace.z之前,我的着色器本地人看起来像这样:

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

    这是针对一些任意选择的像素,尽管每个像素产生类似于clspace矢量的结果。正如您所看到的,clspace.z和clspace.w的大小总是如此相似,以至于除法总是导致~1。

我尝试过什么

  • 在将矩阵上传到设备之前转置矩阵。什么都没有引起我期望的失真,这是我的投影被反转的结果,这向我提出,使用SlimDX的效果框架预先转置矩阵是不必要的。 (这种怀疑后来被证实,SlimDX的效果框架会自动转换设置为效应变量的矩阵。)

  • 在编译效果时通过添加列/行主矩阵打包标志隐式转置矩阵。反转矢量和矩阵在着色器中出现的任何位置的乘法顺序。这些是误导性的尝试,以解决我认为是列/行主要问题。

  • 使用右手投影和查看矩阵而不是左手投影。没有效果,但轴的预期反转。

  • 使用许多不同的近和远投影参数。它们对近距离和远距离剪切距离具有预期效果,但没有解决深度问题。

在尝试追踪导致此问题的任何问题时,我确实耗尽了所有可用资源。我已经完成了几乎所有可能的矩阵排列,操作数顺序和状态设置,我可以理解并提到无数MSDN文章和SO问题,但似乎没有什么适用。非常感谢您对此问题的帮助!

1 个答案:

答案 0 :(得分:0)

您的深度缓冲区始终具有相同的值,因为您的投影矩阵错误。实际写入的值是w值,因此您需要稍微调整矩阵。

DirectX文档很好地解释了perspective transform

基本上你的矩阵应该如下

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;