непредсказуемые ошибки с плавающей запятой при разложении матрицы
Я пытаюсь разложить матрицу перспектив на ближние и дальние расстояния, используя следующую формулу:
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
С тобой что-то не так? математика? Какие у меня есть варианты (без использования 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
2 ответов:
Проблема заключается в том, что чем больше коэффициент
far/near, тем больше значащих цифр требуется для извлеченияfarиз перспективной матрицы.Когда отношение
far/nearувеличивается,m22 = (near+far)/(near-far)приближается к 1.Например, используя
Важной частью результата является дробь. Даже если все остальные операции выполняются с идеальной точностью, в этот момент Вы уже осталось всего 3 значащих цифры. Это очень похоже накатастрофическую отмену . По существу, вы теряете значащую цифру каждый раз, когдаdoubleсnear=0.155иfar=6,000, мы получаемm22 = 1.0000516680014233. Когда это сохраняется какfloat, оно усекается до1.0000516.far/nearумножается на 10. Когдаfarравно6,000,000, значениеm22будет усечено до1.0при сохранении в видеfloat, теряя всю информацию.Я попытался продемонстрировать это в блокноте Jupyter.
Но реальная проблема не только в том, что невозможно извлечьfar, не потеряв точность, но что сама перспективная матрица не является точной.Если вы возьмете вектор в
z=6,000, примените матрицу перспективы, вы не получите z=1.0. Вместо этого, применяя матрицу перспективы к вектору с неверным значениемfar,z=5993.506348даст вамz=1.0. Сама матрица уже ошибочна, поэтому никакой метод извлеченияfarне может помочь.TL; DR: Если вы хотите извлечь
nearиfarиз перспективной матрицы с разумной точностью, вы должны использоватьdouble.Отредактировано: добавлено объяснение реальной проблемы, первоначальный ответ относительно катастрофической отмены - это всего лишь эффект второго порядка.
Матрица проекции описывает отображение из 3D точек сцены в 2D точки видового экрана. Матрица проекции преобразуется из пространства просмотра в пространство клипа. Координаты пространства клипа являютсяоднородными координатами . Координаты в пространстве клипа преобразуются в нормализованные координаты устройства (NDC) в диапазоне от (-1, -1, -1) до (1, 1, 1) путем деления на
wкомпонент координат клипа.Из-за этого точка точка с Z-коориднатом 1 находится на ближней плоскости, а точка с Z-коориднатом 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];

Comments