我试图通过使用以下公式来分解透视矩阵的近距离和远距离:
near = m32 / (m22 - 1);
far = m32 / (m22 + 1);
这里的透视矩阵测试参数:
aspect = 0.782f;
fovy = glm_rad(49.984f);
nearDist = 0.1550385f;
farDist = 6000.340975f;
glm_perspective(fovy, aspect, nearDist, farDist, proj);
这就是我为了获得近似值和远值而做的事情(proj是列主矩阵):
far = proj[3][2] / (proj[2][2] + 1.0f);
near = proj[3][2] / (proj[2][2] - 1.0f)
结果:
near = 0.155039
far = 5993.506348
近似似乎可以接受,但远不是:/如果我为far
使用较小的值,那么我会得到更准确的结果(正确的值是分解的值):
farDist = 600.340975 (near, far): 0.155039 600.319885
farDist = 60.340975f (near, far): 0.155039 60.340946
数学有问题吗?我有什么选择(不使用双重存储矩阵)?
您可以在此处查看透视矩阵公式:https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml
m22 = (near + far) / (near - far)
m32 = 2 * near * far / (near - far)
和实施(行号可能会随时间变化):https://github.com/recp/cglm/blob/master/include/cglm/cam.h#L211
答案 0 :(得分:2)
问题在于far/near
比率越大,从透视矩阵中提取far
需要更多有效数字。
当far/near
比率增加时,m22 = (near+far)/(near-far)
接近1.
例如,将double
与near=0.155
和far=6,000
一起使用,我们得到m22 = 1.0000516680014233
。如果将其存储为float
,则会将其截断为1.0000516
。
结果的重要部分是分数。即使所有其他操作都以完美的准确度完成,此时您只剩下3位有效数字。这与catastrophic cancellation非常相似。
基本上,每次far/near
乘以10时,您都会丢失一个重要数字。当far
为6,000,000
时,m22
的值将被截断为{{1}存储为1.0
时,会丢失所有信息。
我试图在Jupyter Notebook中展示它。
但真正的问题不仅是在不失去精确度的情况下提取float
是不可能的,而且背景矩阵本身并不准确。
如果你在far
处取一个向量,则应用透视矩阵,你将得不到z = 1.0。相反,将透视矩阵应用于具有不正确值z=6,000
,far
的向量将为您提供z=5993.506348
。矩阵本身已经错了,因此没有提取z=1.0
的方法可以提供帮助。
TL; DR:如果您想以合理的准确度从透视矩阵中提取far
和near
,则必须使用far
。
编辑:添加了对真实问题的解释,关于灾难性取消的原始答案只是二阶效应。
答案 1 :(得分:2)
投影矩阵描述了从场景的3D点到视口的2D点的映射。投影矩阵从视图空间变换到剪辑空间。剪辑空间坐标为Homogeneous coordinates。通过除以剪辑的w
分量,剪辑空间中的坐标转换为范围(-1,-1,-1)到(1,1,1)范围内的规范化设备坐标(NDC)坐标。
因此,Z-cooridnate为-1的点位于近平面上,Z-cooridnate为1的点位于远平面上(在标准化的设备空间中)。
要计算到近平面和远平面的距离,您必须通过逆投影矩阵变换近平面上的点和远平面上的点。然后你必须做一个透视鸿沟。到近处或远处平面的距离是结果的反转Z坐标:
mat4 proj;
mat4 invProj = inverse( proj );
vec4 ndcNear(0, 0, -1, 1);
vec4 ndcFar(0, 0, 1, 1);
vec4 ptNear = invProj * ndcNear;
vec4 ptFar = invProj * ndcFar;
near = - ptNear[2] / ptNear[3];
far = - ptFar[2] / ptFar[3];