Matrix Hell - 将3D纹理中的点转换为世界空间

时间:2015-12-24 11:28:02

标签: c++ math matrix directx hlsl

最近我决定在DirectX中为我的3D游戏添加体积雾。我使用的技术来自GPU Pro 6,但是你没有必要拥有这本书的副本以帮助我:)。基本上,体积雾信息存储在3D纹理中。现在,我需要将该3D纹理的每个纹素转换为世界空间。纹理是视图对齐的,我的意思是该纹理的X和Y映射到屏幕的X和Y,并且该纹理的Z在相机前面向前延伸。所以基本上我需要一个功能:

float3 CalculateWorldPosition(uint3 Texel)
{
    //Do math
}

我知道视图矩阵,3D纹理的尺寸(190x90x64或190x90x128),屏幕的投影矩阵等。

然而,不幸的是,并非全部。

您可能知道,DirectX中的深度缓冲区不是线性的。同样的效果需要应用于我的3D纹理 - 纹素需要倾斜,因此相机附近的距离比远处更远,因为相机附近的细节必须比远处更好。但是,我我有一个功能来做这个,如果我错了就纠正我:

//Where depth = 0, the texel is closest to the camera.
//Where depth = 1, the texel is the furthest from the camera.
//This function returns a new Z value between 0 and 1, skewing it
// so more Z values are near the camera.
float GetExponentialDepth(float depth /*0 to 1*/)
{
    depth = 1.0f - depth;

    //Near and far planes
    float near = 1.0f;
    //g_WorldDepth is the depth of the 3D texture in world/view space
    float far = g_WorldDepth;

    float linearZ = -(near + depth * (far - near));
    float a = (2.0f * near * far) / (near - far);
    float b = (far + near) / (near - far);
    float result = (a / -linearZ) - b;
    return -result * 0.5f + 0.5f;
}

这是我当前的功能,试图从纹理元素中找到世界位置(注意它是错误):

float3 CalculateWorldPos(uint3 texel)
{
    //Divide the texel by the dimensions, to get a value between 0 and 1 for
    // each of the components
    float3 pos = (float3)texel * float3(1.0f / 190.0f, 1.0f / 90.0f, 1.0f / (float)(g_Depth-1));
    pos.xy = 2.0f * pos.xy - float2(1.0f, 1.0f);
    //Skew the depth
    pos.z = GetExponentialDepth(pos.z);
    //Multiply this point, which should be in NDC coordinates,
    // by the inverse of (View * Proj)
    return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;
}

然而,投影矩阵对我来说也有点混乱,所以这里是得到3D纹理投影矩阵的线,所以如果它不正确可以纠正我:

//Note that the X and Y of the texture is 190 and 90 respectively.
//m_WorldDepth is the depth of the cuboid in world space.
XMMatrixPerspectiveFovLH(pCamera->GetFovY(), 190.0f / 90.0f, 1.0f, m_WorldDepth)

另外,我已经读过投影矩阵不可逆(它们的逆矩阵不存在)。如果这是真的,那么可能找到(View * Proj)的反转是不正确的,我不确定。

所以,重申一下这个问题,给定一个视图对齐长方体的3D纹理坐标,我怎样才能找到该点的世界位置?

非常感谢,这个问题已经占用了我很多时间!

1 个答案:

答案 0 :(得分:0)

首先让我解释透视投影矩阵的作用。

透视投影矩阵将矢量从视图空间变换到剪辑空间,使得x / y坐标对应于屏幕上的水平/垂直位置,并且z坐标对应于深度。远离相机定位znear单位的顶点被映射到深度0.距离相机zfar单位的顶点被映射到深度1.深度值{{1很快就会增加,而znear前面的深度值只会缓慢变化。

具体而言,给定z坐标,得到的深度为:

zfar

如果你在平均空间深度之后用直线绘制平截头体(例如在每depth = zfar / (zfar - znear) * (z - znear) / z 之后),你得到细胞。前面的细胞比后面的细胞薄。如果绘制足够的单元格,这些单元格将映射到您的纹理像素。在此配置中,它完全按照您的意愿。前面有更多的单元格(导致更高的分辨率)比后面的单元格更多。因此,您可以使用纹理坐标作为深度值(标准化为[0,1]范围)。以下是给定深度值进入视图空间的标准反投影(假设为0.1

Clip Space To View Space

由于以下行,您的代码无效:

znear=1, zfar=10

我们使用4D向量和矩阵是有原因的。如果你只是扔掉第四个尺寸,你会得到错误的结果。相反,做w-clip:

return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;
Btw,4D透视投影矩阵是完全可逆的。只有删除一个维度,才能获得非二次矩阵,这是不可逆的。但这不是我们通常在计算机图形学中所做的事情。但是,这些矩阵也称为投影(但在不同的背景下)。