最近我决定在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纹理坐标,我怎样才能找到该点的世界位置?
非常感谢,这个问题已经占用了我很多时间!
答案 0 :(得分:0)
首先让我解释透视投影矩阵的作用。
透视投影矩阵将矢量从视图空间变换到剪辑空间,使得x / y坐标对应于屏幕上的水平/垂直位置,并且z坐标对应于深度。远离相机定位znear
单位的顶点被映射到深度0.距离相机zfar
单位的顶点被映射到深度1.深度值{{1很快就会增加,而znear
前面的深度值只会缓慢变化。
具体而言,给定z坐标,得到的深度为:
zfar
如果你在平均空间深度之后用直线绘制平截头体(例如在每depth = zfar / (zfar - znear) * (z - znear) / z
之后),你得到细胞。前面的细胞比后面的细胞薄。如果绘制足够的单元格,这些单元格将映射到您的纹理像素。在此配置中,它完全按照您的意愿。前面有更多的单元格(导致更高的分辨率)比后面的单元格更多。因此,您可以使用纹理坐标作为深度值(标准化为[0,1]范围)。以下是给定深度值进入视图空间的标准反投影(假设为0.1
)
由于以下行,您的代码无效:
znear=1, zfar=10
我们使用4D向量和矩阵是有原因的。如果你只是扔掉第四个尺寸,你会得到错误的结果。相反,做w-clip:
return mul(float4(pos, 1.0f), g_InverseViewProj).xyz;
Btw,4D透视投影矩阵是完全可逆的。只有删除一个维度,才能获得非二次矩阵,这是不可逆的。但这不是我们通常在计算机图形学中所做的事情。但是,这些矩阵也称为投影(但在不同的背景下)。