Войти
ПрограммированиеФорумГрафика

Корректный HBAO

Страницы: 1 2 39 10 Следующая »
#0
(Правка: 6:48) 2:39, 24 мар. 2019

Привет.

Задался целью получить математически корректно работающий HBAO.
Читал презентацию от NVIDIA, доку от разработчика HBIL, темы тут, на форуме и кучу разнообразных реализаций по теме.

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

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

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

vec3 get_pos(vec2 uv)
{
  float z  = texture(u_depth_linear, uv).r;
  uv = u_rcp_focal * (-2 * uv + 1);
  return vec3(uv * z, z);
}

vec2 snap_uv(vec2 uv)
{
  return round(uv * u_scr_size) / u_scr_size;
}

float tangent(vec3 P, vec3 S)
{
    return -(P.z - S.z) / length(S.xy - P.xy);
}

const int NumRays  = 16;
const int NumSteps = 8;
const float R      = 0.2;

void main(void)
{
  vec3 P = get_pos(v_uv);

  float radius = R * 0.5 * u_proj_scale / P.z; // радиус сферы, спроецированной на экран
  float angle_step = 2.0 * PI / NumRays;
  vec2  march_step = vec2(radius / (NumSteps + 1)) / u_scr_size;

  float ao = 0;
  float angle = 0;
  for (int i=0; i<NumRays; ++i)
  {
    vec2 dir = vec2(cos(angle), sin(angle));
    angle += angle_step;

    vec2 ray_step = dir * march_step;
    vec2 ray = ray_step;
    float maxAngle = 0.0;
    float ray_ao = 0;
    for (int j=0; j<NumSteps; ++j)
    {
      vec2 suv = v_uv + snap_uv(ray);
      ray += ray_step;

      vec3 S = get_pos(suv);
      float tg = tangent(P, S);
      float angle = atan(tg); // угол между экранной плоскостью и горизонтом для текущего сэмпла

      if (angle > maxAngle)
      {
        ray_ao += sin(angle) - sin(maxAngle);
        maxAngle= angle;
      }
    }
    ao += ray_ao; // правильно ли это?
  }

  ao /= NumRays; // правильно ли это?
  ao = 1 - ao;

  fragColor = vec4(vec3(ao), 1);
}

Вопросы:

maxAngle = 0.0 следует инициализировать не нулем, а углом между касательной к текущему фрагменту и экранной плоскостью. Отсюда вопрос - можно ли рассчитывать угол горизонта не относительно касательной (как делают в большинстве доков), а относительно нормали? Тем самым можно исключить расчет касательной и оставить maxAngle=0.

Нужно ли аккумулировать вклад АО для каждого сэмпла или только для тех, которые находятся выше текущей линии горизонта? Теория и здравый смысл говорят о втором варианте, но в некоторых реализациях встречается первый.

Как правильно нормализовывать накопленные значения АО? Опять же, в разных реализациях встречаются совершенно разные подходы.

Повторюсь, хочется сделать не просто чтобы работало, а чтобы работало правильно.


#1
(Правка: 4:19) 4:15, 24 мар. 2019

San
> Получается, что нет референса, с которым можно было бы сравнить, чтобы убедиться в правильности расчетов.
ещё как есть. сравнивай освещённость для случаев, у которых есть аналитически точное решение. например, у плоскости должна быть освещённость ровно 1, у прямого двугранного угла — ровно 0.5, итп. я отлаживаю, например, так:

resColor = ((abs(occlusion - 0.5f) < 0.01f) ? vec4(0.0f, 1.0f, 0.0f, 1.0f) : vec4(1.0f, 0.0f, 0.0f, 1.0f));
и если смотреть на двугранный угол, то вся картинка должна быть красной, а ровно внутри всех двугранных углов должен быть зелёный, причём он не должен зависеть от угла обзора.

San
> Отсюда вопрос - можно ли рассчитывать угол горизонта не относительно
> касательной (как делают в большинстве доков), а относительно нормали?
проблема в том, что интегрировать в базисе, который не смотрит на камеру, сложнее, так как вклад каждого направления будет уже не 1/n.

San
> Как правильно нормализовывать накопленные значения АО? Опять же, в разных
> реализациях встречаются совершенно разные подходы.
очевидно, результат зависит от того, как ты считаешь сам интеграл, потому что математически эквивалентный результат можно получить, интегрируя в разных базисах. например, можно интегрировать в базисе, построенном на плоскости проекции камеры, а можно — на плоскости, перпендикулярной view лучу. из-за перспективной проекции эти плоскости оказываются разные и во многих пейперах из-за этого путаница.

вообще ambient occlusion часто применяют с миллионом кривых нефизичных хаков вроде falloff radius'а, которые смазывают все косяки реализации. если же реализовывать global illumination на подобных формулах, то все эти косяки очень быстро всплывают и становятся очевидны.

#2
4:54, 24 мар. 2019

Suslik
Тебя-то я и ждал )

> вся картинка должна быть красной, а ровно внутри всех двугранных углов должен быть зелёный
Именно так и делаю. Но ведь это не единственный критерий правильности. У меня был случай, когда при некорректных вычислениях это условие соблюдалось, т.е. стенки были чисто белыми, а в самом их стыке было 0.5.

> интегрировать в базисе, который не смотрит на камеру, сложнее, так как вклад каждого направления будет уже не 1/n
Не врубаюсь. Касательная вычисляется из нормали. Условно, касательная, это та же нормаль, только повернутая на PI/2. И если для вычислений с касательной мы используем синус угла горизонта, то почему нельзя использовать косинус угла между горизонтом и нормалью? По идее, ведь должно получиться ровно то же. И в сумме они должны дать единицу. Или нет? )

> можно интегрировать в базисе, построенном на плоскости проекции камеры, а можно — на плоскости, перпендикулярной view лучу
Стоп, а это не одно и то же? Или речь о том, что в первом случае все ортогонально, а во втором - view луч = normalize(view_pos)? Похоже, вылезают тонкости, которые на первый взгляд казались неочевидными.

> если же реализовывать global illumination на подобных формулах, то все эти косяки очень быстро всплывают и становятся очевидны
Такая цель и стоит. Хотя бы локально посчитать GI, но при этом точно и качественно. Начал с самого начала - HBAO без рандомизации и оптимизаций, чтобы все работало как часы.
Кстати, спрошу еще про GI, раз уж такое дело. Правильно ли я понимаю, что при расчете АО мы получаем телесный угол для текущего фрагмента и дальше нам нужно еще раз потрейсить в его направлении, найти пересечение с глубиной и прочитать радианс с учетом ширины конуса этого угла? Т.е. в любом случае нужен еще один трейс?

#3
(Правка: 5:35) 5:34, 24 мар. 2019

San
> Именно так и делаю. Но ведь это не единственный критерий правильности. У меня
> был случай, когда при некорректных вычислениях это условие соблюдалось, т.е.
> стенки были чисто белыми, а в самом их стыке было 0.5.
на самом деле если хотя бы этот тест выполняется при любых поворотах камеры, то это уже означает, что считается не полная ерунда. по сути единственное, что даст false positive на таком тесте — это если неправильно учитывается анизотропия направлений расчёта. чтобы её проверить, можно пересечь 3 перпендикулярных плоскости (внутренние углы бокса, например) — в них освещённость должна быть ровно 25%, вне зависимости от угла обзора.

San
> Не врубаюсь. Касательная вычисляется из нормали. Условно, касательная, это та
> же нормаль, только повернутая на PI/2. И если для вычислений с касательной мы
> используем синус угла горизонта, то почему нельзя использовать косинус угла
> между горизонтом и нормалью? По идее, ведь должно получиться ровно то же. И в
> сумме они должны дать единицу. Или нет? )
а, я понял, что ты имеешь в виду. ну так используй, кто ж тебе запрещает. разницы-то никакой нет по сути, варианты математически эквивалентны.

San
> Или речь о том, что в первом случае все ортогонально, а во втором - view луч =
> normalize(view_pos)?
как раз наоборот. плоскости камеры (в смысле zfar и znear, например) на самом деле перпендикулярны лучу normalize(view_pos) только в центре экрана. в общем же случае из-за формы view frustum его рёбра вовсе не перпендикулярны его основаниям. поэтому тесты на по сверке нужно выполнять не только в центре экрана, но и на периферии, причём лучше использовать большой fov, чтобы влияние перспективы было очевидным.

San
> Правильно ли я понимаю, что при расчете АО мы получаем телесный угол для
> текущего фрагмента и дальше нам нужно еще раз потрейсить в его направлении,
> найти пересечение с глубиной и прочитать радианс с учетом ширины конуса этого
> угла? Т.е. в любом случае нужен еще один трейс?
здесь нет никаких "нужно", потому что не существует одного правильного решения. делать можно по-разному. я делаю не так. я использую угол раствора конуса, образованного старым и новым горизонтом.

#4
(Правка: 12:53) 6:15, 24 мар. 2019

Suslik
> варианты математически эквивалентны.
Отлично. Были какие-то сомнения насчет такого варианта. Странно, что в большинстве реализаций так не делают, это же очевидно. Ведь в этом случае можно инициализировать горизонт нулем.
Upd. Увы нет, ведь все равно нормаль придется проецировать на плоскость луча. А чтобы получить касательную, нужно луч спроецировать на плоскость, перпендикулярную нормали. Те же яйца.

> плоскости камеры (в смысле zfar и znear, например) на самом деле перпендикулярны лучу normalize(view_pos) только в центре экрана
Я об этом и говорю. Уточню: "в базисе, построенном на плоскости проекции камеры" - это когда у всех базисов оси Z параллельны ("все ортогонально"), а "на плоскости, перпендикулярной view лучу" - когда Z сходятся к центру в направлении камеры?

> причём лучше использовать большой fov, чтобы влияние перспективы было очевидным
Во всех реализациях, что мне попадались, не было явного учета перспективных искажений. Ведь, по идее, значение интеграла получится одним и тем же, что в центре экрана, что с краю.
Upd: Получится, но только если учитывать перспективную проекцию. Такое нашлось только в доке про HBIL.
Переформулирую: если весь экран занимает стена, параллельная плоскости экрана (даже не обязательно параллельная), то по всему экрану, по всем направлениям у фрагментов должны получаться нулевые горизонты?

> угол раствора конуса, образованного старым и новым горизонтом
А как в этом случае разруливается ситуация с плоскими поверхностями, у которых все горизонты - по нулям и окклюжена нет? Ну т.е. условная комната у которой одна стенка светится.

#5
(Правка: 19:55) 17:02, 25 мар. 2019

Такс, вроде все понятно, но есть нюансы.

Чтобы учесть то, что view вектор не перпендикулярен экрану (V = normalize(P)), будем считать все в его системе координат. Берем прям кватернион q_v = V -> vec3(0,0,-1) и крутим им нормаль.
Проецируем vec2 луча сэмплинга на плоскость нормали, получаем касательную и начальный угол горизонта.
Считаем горизонт как разницу view positions текущего фрагмента и сэмпла (H = P - S), его тоже переводим в пространство view вектора. Считаем угол для горизонта как atan(H.z / length(H.xy)). Дальше все традиционно - учет разницы синусов.

Получаем хрень:
Изображение

Смущает то, что при тупом расчете без учета view вектора и без инициализации горизонтов получается почти правильная картинка:
Изображение
Т.е. вот прям один dot product во внутреннем цикле.
Плоские поверхности четко белые, двугранный угол тоже близок к 0.5:
Изображение
Но из-за угла обзора (или еще по какой-то причине) углы не равномерно зеленые и если покрутить камерой, то все ожидаемо слегка плавает.

#6
22:45, 25 мар. 2019

Есть исчерпывающая презентация от nvidia https://developer.download.nvidia.com/presentations/2008/SIGGRAPH… AO_SIG08b.pdf

+ Показать

#7
1:12, 26 мар. 2019

BingoBongo
Читал и много раз. Вся теория понятна, но есть нюансы.
Буду долбить пока не осилю )

#8
(Правка: 7:03) 7:00, 26 мар. 2019

Додолбил )
Есть мелкие косяки, но не смог удержаться и запилил что-то, похожее на вторичку.
Изображение

#9
(Правка: 7:03) 7:02, 26 мар. 2019

чё-то пока достаточно отдалённо, если честно. с чего всё такое белёсое? ты забыл что ли вторичное освещение на альбедо домножить как минимум? сколько мс на кадр?

#10
(Правка: 7:11) 7:05, 26 мар. 2019

Suslik
Еще пока HBAO добиваю, а тут тупо добавил вторичку (которая считается тяп-ляп) сложением.
Какие там мс на кадр. Тут никаких оптимизаций, одни пессимизации для того, чтобы не запутаться.
Думал дым пойдет из вентиляторов )

А вот проблема с зависимостью от view вектора осталась.

#11
7:33, 26 мар. 2019

Вот с альбедо и с учетом нормали
Изображение

А вот ништяки )
Изображение

#12
7:38, 26 мар. 2019

San
уже лучше, но местами всё равно странно. рекомендую для тестов использовать самый неудобный случай — мелкий яркий источник вторичного света, висящий в воздухе. освещение от него можно напрямую сравнивать с освещением от точечного источника, тогда очень хорошо все косяки будут видны.

#13
11:16, 26 мар. 2019

San
Это воксели или RSM? Наврядли PRT.

#14
11:52, 26 мар. 2019

San
> HBAO
lookid
> Это воксели или RSM? Наврядли PRT.

Страницы: 1 2 39 10 Следующая »
ПрограммированиеФорумГрафика