Повышение точности пересечения лучей и эллипсоидов

Мне нужно повысить точность работы в одном из моих шейдерных фрагментов GLSL с атмосферным рассеянием, который вычисляет пересечение между одиночным лучом и выровненным по оси эллипсоидом.

Это основная функция моего шейдера атмосферного рассеяния. Старый оригинальный шейдер был включен floats и для нормального рендеринга было хорошо, но после добавления зума я обнаружил, что при относительно небольших расстояниях точность теряется. На поплавках полезные расстояния для Земли составляли только до 0,005 а.е. (астрономическая единица). Поэтому я попытался перенести критическую функцию на double и это помогает, так что теперь полезное расстояние составляет около 1,0 а.е. (с небольшими артефактами)

Это double версия функции внутри Fragment Shader (в старом стиле используются устаревшие вещи!!!)

#extension GL_ARB_gpu_shader_fp64 : enable
double abs(double x) { if (x<0.0) x=-x; return x; }
// compute length of ray(p0,dp) to intersection with ellipsoid((0,0,0),r) -> view_depth_l0,1
// where r.x is elipsoid rx^-2, r.y = ry^-2 and r.z=rz^-2
float view_depth_l0=-1.0,view_depth_l1=-1.0;
bool _view_depth(vec3 _p0,vec3 _dp,vec3 _r)
    {
    double a,b,c,d,l0,l1;
    dvec3 p0,dp,r;
    p0=dvec3(_p0);
    dp=dvec3(_dp);
    r =dvec3(_r );
    view_depth_l0=-1.0;
    view_depth_l1=-1.0;
    a=(dp.x*dp.x*r.x)
     +(dp.y*dp.y*r.y)
     +(dp.z*dp.z*r.z); a*=2.0;
    b=(p0.x*dp.x*r.x)
     +(p0.y*dp.y*r.y)
     +(p0.z*dp.z*r.z); b*=2.0;
    c=(p0.x*p0.x*r.x)
     +(p0.y*p0.y*r.y)
     +(p0.z*p0.z*r.z)-1.0;
    d=((b*b)-(2.0*a*c));
    if (d<0.0) return false;
    d=sqrt(d);
    l0=(-b+d)/a;
    l1=(-b-d)/a;
    if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; }
    if (l0<0.0)          { a=l0; l0=l1; l1=a; }
    if (l0<0.0) return false;
    view_depth_l0=float(l0);
    view_depth_l1=float(l1);
    return true;
    }
  • вход луча и радиусов ^-2 эллипсоида
  • выходное расстояние от р0 до пересечений

    геометрический обзор

  • точность входных и выходных переменных float (достаточно)

Вот так это выглядит после портирования на Double

артефакты

Итак, вопрос: Q1. Как повысить точность этой функции?

  • целевая точность для view_depth_l0, view_depth_l1 является +/- 20 m для расстояния |p0|=100 AU

Это было бы идеально, сейчас кажется, что +/- 5 км на расстоянии 10 AU, что плохо. Даже 10-кратное точное вычисление будет огромным шагом вперед, есть идеи?

[edit1] l0, l1 range

Я ошибся с плавающей точкой конвертации view_depth_l0,view_depth_l1 является причиной артефактов. После смещения на относительное расстояние точность значительно улучшилась. Я только добавил это:

    // relative shift to preserve accuracy
    const double m0=1000000000.0; // >= max view depth !!!
    if (l0>m0){ a=floor(l0/m0)*m0; a-=m0; if (l1>l0) l1-=a; l0-=a; }

до этого:

    view_depth_l0=float(l0);
    view_depth_l1=float(l1);
    return true;
    }

Остальная часть шейдерной ручки l0,l1 В качестве относительных значений в любом случае получим следующее:

артефакты

для расстояний до 10,0 а.е. это нормально (артефакты заметны только в очень больших масштабах), новые артефакты возникают скорее всего в других местах, поэтому мне придется продолжить исследования, когда у меня будет время и желание.

[edit2] смещение p0 вдоль dp ближе к (0,0,0)

Реализация требует относительно дорогих функций нормализации и длины, и результат без смещения диапазона (edit1) был немного лучше, чем необработанная функция, но улучшение не слишком большое. С изменением диапазона (edit1) результат тот же, что и раньше, поэтому это не так. Мой вывод заключается в том, что все оставшиеся артефакты не вызваны самой функцией просмотра.

Я попробую портировать шейдер на #version 400 + fp64 в целом, чтобы проверить, не слишком ли округлены входные данные с плавающей точкой

[edit3] фактический исходный код

#extension GL_ARB_gpu_shader_fp64 : enable
double abs(double x) { if (x<0.0) x=-x; return x; }
// compute length of ray(p0,dp) to intersection with ellipsoid((0,0,0),r) -> view_depth_l0,1
// where r.x is elipsoid rx^-2, r.y = ry^-2 and r.z=rz^-2
float view_depth_ll= 0.0, // shift to boost accuracy
      view_depth_l0=-1.0, // view_depth_ll+view_depth_l0 first hit
      view_depth_l1=-1.0; // view_depth_ll+view_depth_l1 second hit
const double view_depth_max=100000000.0; // > max view depth
bool _view_depth(vec3 _p0,vec3 _dp,vec3 _r)
    {
    dvec3 p0,dp,r;
    double a,b,c,d,l0,l1;
    view_depth_ll= 0.0;
    view_depth_l0=-1.0;
    view_depth_l1=-1.0;
    // conversion to double
    p0=dvec3(_p0);
    dp=dvec3(_dp);
    r =dvec3(_r );
    // quadratic equation a.l.l+b.l+c=0; l0,l1=?;
    a=(dp.x*dp.x*r.x)
     +(dp.y*dp.y*r.y)
     +(dp.z*dp.z*r.z);
    b=(p0.x*dp.x*r.x)
     +(p0.y*dp.y*r.y)
     +(p0.z*dp.z*r.z); b*=2.0;
    c=(p0.x*p0.x*r.x)
     +(p0.y*p0.y*r.y)
     +(p0.z*p0.z*r.z)-1.0;
    // discriminant d=sqrt(b.b-4.a.c)
    d=((b*b)-(4.0*a*c));
    if (d<0.0) return false;
    d=sqrt(d);
    // standard solution l0,l1=(-b +/- d)/2.a
    a*=2.0;
    l0=(-b+d)/a;
    l1=(-b-d)/a;
    // alternative solution q=-0.5*(b+sign(b).d) l0=q/a; l1=c/q; (should be more accurate sometimes)
//  if (b<0.0) d=-d; d=-0.5*(b+d);
//  l0=d/a;
//  l1=c/d;
    // sort l0,l1 asc
    if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; }
    if (l0<0.0)          { a=l0; l0=l1; l1=a; }
    if (l0<0.0) return false;
    // relative shift to preserve accuracy after conversion back float
    if (l0>view_depth_max){ a=floor(l0/view_depth_max)*view_depth_max; a-=view_depth_max; view_depth_ll=float(a); if (l1>l0) l1-=a; l0-=a; }
    // conversion back float
    view_depth_l0=float(l0);
    view_depth_l1=float(l1);
    return true;
    }

Портирование остальных шейдеров на удвоение не имеет никакого эффекта. Единственное, что могло бы улучшить это double входные данные (вход double но GL преобразовать его в float), но мой нынешний GLSL HW не позволяет 64 bit интерполяторы

Q2. Есть ли способ пройти double интерполяторы из вершины в фрагментный шейдер?

Что-то вроде varying dvec4 pixel_pos; в старом стиле GLSL или out smooth dvec4 pixel_pos; в профиле ядра

0 ответов

Другие вопросы по тегам