最近我决定在我的 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
)
由于这一行,您的代码无法运行:
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/