我正在尝试通过使用以下公式分解透视矩阵的近距离和远距离:
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。
例如,将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.0
时将被截断为float
,从而丢失所有信息。
我试图用Jupyter Notebook演示它。
但是,真正的问题不仅在于在不损失精度的情况下提取far
是不可能的,而且透视矩阵本身也不准确。
如果您在z=6,000
处采用向量,则应用透视矩阵,则不会得到z = 1.0。相反,将透视图矩阵应用于具有far
值不正确的向量,z=5993.506348
将为您提供z=1.0
。矩阵本身已经是错误的,因此提取far
的任何方法都无济于事。
TL; DR:如果要从透视矩阵中以合理的精度提取near
和far
,则必须使用double
。
编辑:添加了对实际问题的解释,有关灾难性取消的原始答案只是二阶效应。