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

标签 c++ math matrix directx hlsl

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

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

我知道 View 矩阵、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) 的逆函数是不正确的,我不确定。

那么,重申一下这个问题,给定一个与 View 对齐的长方体的 3D 纹理坐标,我如何才能找到该点的世界位置?

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

最佳答案

先解释一下透视投影矩阵的作用。

透视投影矩阵将一个 vector 从 View 空间变换到裁剪空间,这样 x/y 坐标对应于屏幕上的水平/垂直位置,z 坐标对应于深度。距离相机 znear 个单位的顶点映射到深度 0。距离相机 zfar 个单位的顶点映射到深度 1。深度znear 后面的值增加得非常快,而 zfar 前面的深度值变化缓慢。

具体来说,给定一个 z 坐标,得到的深度为:

depth = zfar / (zfar - znear) * (z - znear) / z

如果您在深度均匀的空格后(例如,在每 0.1 之后)用线绘制平截头体,您会得到单元格。前面的电池比后面的电池薄。如果您绘制足够多的单元格,这些单元格会映射到您的纹素。在此配置中,它完全如您所愿。前面的单元格(导致分辨率更高)比后面的单元格多。所以你可以只使用纹素坐标作为深度值(归一化到 [0,1] 范围)。这是给定深度值到 View 空间的标准反投影(假设 znear=1, zfar=10)

Clip Space To View Space

由于这一行,您的代码无法运行:

return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;

我们使用 4D vector 和矩阵是有原因的。如果你只是丢掉第四维,你会得到错误的结果。相反,执行 w-clip:

float4 transformed = mul(float4(pos, 1.0f), g_InverseViewProj);
return (transformed / transformed.w).xyz;

顺便说一句,4D 透视投影矩阵是完全可逆的。只有移除一维,才会得到不可逆的非二次矩阵。但这不是我们通常在计算机图形学中所做的。然而,这些矩阵也称为投影(但在不同的上下文中)。

关于c++ - Matrix Hell - 将 3D 纹理中的点转换为世界空间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34452106/

相关文章:

c++ - 在 std::map 中使用 TiXmlElement vector

c++ - 在 C++ 中,我需要创建一个程序,当输入某个数字时循环并停止,然后显示最大值和最小值

c++ - 目标及其 SO 依赖项的 CMake `INSTALL`

c - 对表示大整数的结构进行算术运算

math - 复数应该如何呈现?

python - Python 中的 3 维矩阵

c++ - 如何在 C++ 中创建带返回值的内联作用域?

math - 掌握 GIS 数学,从哪里开始?

连接 2 个矩阵

Python 类中的数组构造