Matrix Hell - 将 3D 纹理中的点转换为世界 Space
Matrix Hell - Transforming a Point in a 3D Texture to World Space
最近我决定在我的 DirectX 3D 游戏中添加体积雾。我使用的技术来自 GPU Pro 6 这本书,但您没有必要拥有这本书的副本来帮助我 :)。基本上,体积雾信息存储在 3D 纹理中。现在,我需要将该 3D 纹理的每个纹素转换为世界 space。纹理是视图对齐的,我的意思是该纹理的 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 纹理坐标,我如何才能找到该点的世界位置?
非常感谢,这个问题占用了我很多时间!
先解释一下透视投影矩阵的作用
透视投影矩阵将向量从视图space转换为剪辑space,使得x/y坐标对应于屏幕上的horizontal/vertical位置和z坐标对应于深度。距离相机 znear
个单位的顶点映射到深度 0。距离相机 zfar
个单位的顶点映射到深度 1。紧跟在 [=13 之后的深度值=] 增加得非常快,而 zfar
前面的深度值变化缓慢。
具体来说,给定一个 z-coordinate,得到的深度是:
depth = zfar / (zfar - znear) * (z - znear) / z
如果你在甚至 spaces 深度之后(例如,在每 0.1
之后)用线绘制平截头体,你就会得到单元格。前面的电池比后面的电池薄。如果您绘制足够多的单元格,这些单元格会映射到您的纹素。在此配置中,它完全如您所愿。前面的单元格(导致分辨率更高)比后面的单元格多。所以你可以只使用纹素坐标作为深度值(归一化到 [0,1] 范围)。这是给定深度值到视图 space 的标准反投影(假设 znear=1, zfar=10
)
由于这一行,您的代码无法运行:
return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;
我们使用 4D 向量和矩阵是有原因的。如果你只是丢掉第四维,你会得到错误的结果。相反,执行 w-clip:
float4 transformed = mul(float4(pos, 1.0f), g_InverseViewProj);
return (transformed / transformed.w).xyz;
顺便说一句,4D透视投影矩阵是完全可逆的。只有去掉一维,才能得到一个不可逆的non-quadratic矩阵。但这不是我们通常在计算机图形学中所做的。但是,这些矩阵也称为投影(但在不同的上下文中)。
最近我决定在我的 DirectX 3D 游戏中添加体积雾。我使用的技术来自 GPU Pro 6 这本书,但您没有必要拥有这本书的副本来帮助我 :)。基本上,体积雾信息存储在 3D 纹理中。现在,我需要将该 3D 纹理的每个纹素转换为世界 space。纹理是视图对齐的,我的意思是该纹理的 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 纹理坐标,我如何才能找到该点的世界位置?
非常感谢,这个问题占用了我很多时间!
先解释一下透视投影矩阵的作用
透视投影矩阵将向量从视图space转换为剪辑space,使得x/y坐标对应于屏幕上的horizontal/vertical位置和z坐标对应于深度。距离相机 znear
个单位的顶点映射到深度 0。距离相机 zfar
个单位的顶点映射到深度 1。紧跟在 [=13 之后的深度值=] 增加得非常快,而 zfar
前面的深度值变化缓慢。
具体来说,给定一个 z-coordinate,得到的深度是:
depth = zfar / (zfar - znear) * (z - znear) / z
如果你在甚至 spaces 深度之后(例如,在每 0.1
之后)用线绘制平截头体,你就会得到单元格。前面的电池比后面的电池薄。如果您绘制足够多的单元格,这些单元格会映射到您的纹素。在此配置中,它完全如您所愿。前面的单元格(导致分辨率更高)比后面的单元格多。所以你可以只使用纹素坐标作为深度值(归一化到 [0,1] 范围)。这是给定深度值到视图 space 的标准反投影(假设 znear=1, zfar=10
)
由于这一行,您的代码无法运行:
return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;
我们使用 4D 向量和矩阵是有原因的。如果你只是丢掉第四维,你会得到错误的结果。相反,执行 w-clip:
float4 transformed = mul(float4(pos, 1.0f), g_InverseViewProj);
return (transformed / transformed.w).xyz;
顺便说一句,4D透视投影矩阵是完全可逆的。只有去掉一维,才能得到一个不可逆的non-quadratic矩阵。但这不是我们通常在计算机图形学中所做的。但是,这些矩阵也称为投影(但在不同的上下文中)。