我正在尝试通过使用以下公式分解透视矩阵的近距离和远距离:

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

Near似乎可以接受,但farer则不行:/如果我为far使用较小的值,那么我会得到更准确的结果(正确的值是分解值):
farDist = 600.340975 (near, far): 0.155039 600.319885
farDist = 60.340975f (near, far): 0.155039 60.340946

数学有问题吗?我有什么选择(不使用double来存储矩阵)?

您可以在此处查看透视矩阵公式: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

最佳答案

问题是far/near比值越大,从透视矩阵中提取far所需的位数就越大。

far/near比率增加时,m22 = (near+far)/(near-far)接近1。

例如,将doublenear=0.155far=6,000一起使用,我们得到m22 = 1.0000516680014233。当将其存储为float时,它将被截断为1.0000516

结果的重要部分是分数。即使所有其他取词均以完美的准确性完成,但此时您只剩下3个有效数字。这与catastrophic cancellation非常相似。

本质上,每次将far/near乘以10时,您将丢失一个有效数字。当far6,000,000时,将m22的值存储为1.0时将被截断为float,从而丢失所有信息。

我试图用Jupyter Notebook演示它。

但是,真正的问题不仅在于在不损失精度的情况下提取far是不可能的,而且透视矩阵本身也不准确。

如果您在z=6,000处采用向量,则应用透视矩阵,则不会得到z = 1.0。相反,将透视图矩阵应用于具有far值不正确的向量,z=5993.506348将为您提供z=1.0。矩阵本身已经是错误的,因此提取far的任何方法都无济于事。

TL; DR:如果要从透视矩阵中以合理的精度提取nearfar,则必须使用double

编辑:添加了对实际问题的解释,有关灾难性取消的原始答案只是二阶效应。

10-08 09:33