непредсказуемые ошибки с плавающей запятой при разложении матрицы



Я пытаюсь разложить матрицу перспектив на ближние и дальние расстояния, используя следующую формулу:

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

129   2  

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 значащих цифры. Это очень похоже накатастрофическую отмену . По существу, вы теряете значащую цифру каждый раз, когда 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];
    Ничего не найдено.

Добавить ответ:
Отменить.