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

本文关键字:空间 深度计算 不准确 投影 | 更新日期: 2023-09-27 18:13:29

目前正在使用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);
    }
}

着色器的目的是在彩色顶点的点列表上操作,使用几何着色器将每个顶点扩展为一个标记的"冒充者"球体,然后将最终场景写入渲染目标,同时将每个像素的调整深度(由"球体"法线偏移的眼空间深度)写入深度缓冲区。目前,这个配置使正确渲染场景,如下图所示。

https://i.stack.imgur.com/G4omk.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}

What I Know

  • 通过使用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);
    }
    
  • 我的模型矩阵只是单位矩阵,在它出现的地方被省略。

  • 使用图形调试器,我能够通过像素着色器步进到剪辑空间深度计算的点。在着色器尝试划分剪贴空间之前。Z乘clipspace。W,我的着色器局部看起来像这样:

    https://i.stack.imgur.com/Nfyg4.png

    这是针对一些任意选择的像素,尽管每个像素对clspace向量产生类似的结果。如您所见,clspace。Z和clspace。W的大小总是如此相似,因此除法总是得到~1。

What I've try

  • 将矩阵上传到设备之前进行转置。什么也没做,只是造成了我期望的投影倒转的结果,这向我表明,在SlimDX的效果框架中,预转置矩阵是不必要的。(这一怀疑后来被证实,SlimDX的效果框架自动将矩阵设置为效果变量。)

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

  • 使用右手投影和视图矩阵,而不是左手的。

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

我已经用尽了所有可用的资源,试图找出导致这个问题的原因。我已经通过了几乎所有可能的矩阵排列,操作数顺序和状态设置,我可以理解,并参考了无数MSDN文章和SO问题,但似乎没有什么适用。非常感谢您在这个问题上的帮助!

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

你的深度缓冲区总是有相同的值写入它,因为你有你的投影矩阵错误。实际写入的值是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;