Подскажите, как правильно делать parallax occlusion на кривой террейне.
Проблема в артефактах на изгибах. Нормали находятся на вершинах и интерполируются. tangent space Вычисляется в вершинном шейдере и по идее он для каждого фрагмента приходит правильным.
Но в самом фрагментном шейдере, при семплинге, во время реймарчинга у меня нет нового тангентного пространства. А если бы и был, то я бы не мог по нему менять вектор марчинга, т.к. он уводил бы луч в другую сторону(Красный на рисунке).

Верхний рисунок - кривая поверхность террейна.
Нижний - как луч должен проходить в текстуре.
Синие верктора - нормали.
Есть идея делать dot текущего вектора с текущей нормалью(dot(normal, cur_vector)) и смотреть разницу с предыдущей итерацией и на основе этого менять направление марчинга. Но как? И правильный ли это способ...
И если вводит cone marching, то как он ложен работать и ка менять размеры конуса в зависимости от кривизны.
Может кто уже такое делал, подскажите.
Хотя нет. вычислять новую проекцию луча для каждого шага марчинга вроде работает. Я почему-то не учел, что view direction не меняется, а значит и луч пойдет правильно.
Но все равно куча искажений. Будут думать как решать. Может точность float маленькая.
И надо будет придумать как от тяжелых вычислений избавиться.
Параллакс используется для определения расстояния до звезд, а occlusion - это отсечение,
т.о. parallax occlusion - это отсечение звезд не попадающих в область отображения, к террейну оно не имеет никакого отношения.
AWPStar
Давай посмотрим. В своем посте ты пишешь: "Tangent space вычисляется в вершинном шейдере... нормали интерполируются".
Вот тут и зарыта собака.
Как делают обычно (для плоскостей): Вычисляют вектор взгляда (ViewDir) в Tangent Space в вершинном шейдере и интерполируют его. Ну и для плоской стены это как бы работает.
Но ты-то хочешь использовать это дело для объекта со сложной формой. На кривой поверхности TBN-матрица вращается от пикселя к пикселю. Интерполяция векторов линейна, а вращение - нет.
Тут нам надо передавать во фрагментный шейдер собственно саму TBN матрицу (или интерполировать нормаль/тангенс и пересчитывать битангенс на лету). И переводить вектор взгляда в Tangent Space строго в каждом пикселе заново.
Если же ты пытаешься интерполировать уже трансформированный вектор, то на изгибе этот вектор как раз-таки и будет указывать "в никуда", потому что базис, в котором он был вычислен (вершина), не совпадает с базисом, где он применяется (середина кривого полигона).
Ну и далее. В Tangent Space (пространстве текстуры) луч всегда прямой.
Это сама поверхность как бы изгибается вокруг луча. Но поскольку мы делаем Ray Marching в пространстве 2D-текстуры (UV + Depth), то мы обязаны считать, что идем по прямой линии.
А вот ежели ты начинаешь внезапно гнуть луч внутри цикла POM (ну вот как ты хочешь, типа как бы "менять вектор марчинга"), тогда ты обязательно получишь дичайшие искажения, потому что ты пытаешься имитировать кривизну поверхности, изменяя траекторию света. То есть, ты таким образом делаешь как бы двойное преобразование, и вот оно уже способно привести к хаосу.
И снова далее. На твоем верхнем рисунке видно, что луч (зеленый) делает абсолютное пролетайло сквозь геометрию насквозь. Ну так вот. POM - это фейк. На самом деле ты имеешь дело с этакой коробкой как бы с углублением. На сильных изгибах (особенно на краях холмов) луч в Tangent Space может уйти ниже плоскости полигона, а то вовсе ускакать за пределы UV-координат.
Это-то и вызывает артефакты "среза" или "растягивания".
Ну и чего с этим делать? Надо попробовать тогда технику Curved Relief Mapping (это сложно, требует аппроксимации кривизны через вторую производную UV), либо просто забить на это (как делают в 99% игр), настраивая discard для пикселей, где луч "провалился" слишком глубоко.
В общем, если совсем коротенько:
1. Перенеси расчет TangentViewDir в пиксельный шейдырь.
2. Не пытайся творить уличную магию с формой луча, Дэвид Блейн.
3. Учти, что на острых углах POM всегда будет ломаться, потому что это ограничение технологии (parallax mapping не может корректно отобразить силуэт, если он не использует Prism Parallax Occlusion Mapping, что очень дорого). Ну либо тебе нужна тесселяция с дисплейсом, или я не знаю что еще предложить.
Лис®©™
Спасибо за подробный ответ.
Да я начал пересчитывать направление вектора(проекцию для касательной) на каждой итерации.
Оно вроде как выглядит правильно.
norm = texture(normalmap, cuv.xz / 128.0).xyz;
tangent = vec3(1.0, 0.0, 0.0);
bitangent = -cross(norm, tangent);
tangent = cross(norm, bitangent);
vec_proj= vec3( dot(viewDir, tangent), dot(viewDir, bitangent), dot(viewDir, norm) ).xzy;
step_s = (layer_h/ float(layers)) /(-vec_proj.y);У меня тут некоторые особенности со знаками, но они не сильно важны для понимания.
Главное, что это делается для каждого шага Ray marching.
Это по сути и меняет траекторию марчинга, но визуально все на своих местах.
Надо вычистить шум и артефакты, из-за них может не видны какие-то другие искажения.
И высота слоя преувеличена чтобы на видео лучше эффект и искажения были видены.

discard при вылете из текстуры пока не делаю, т.к. надо отключать face culling. Ну и куча артефактов, которые надо поправить.
Главное что геометрия вроде как выглядит правильной.
AWPStar
> Спасибо за подробный ответ.
Мог бы и сам нейросеть использовать чтобы спросить.
Blueprint
> Мог бы и сам нейросеть использовать чтобы спросить.
Зачем же тогда форум? Эх отпадает надобность то ((
AWPStar
Было нечто такое . попробуй рефаймент параллакс
innuendo
Не могу детали по алгоритму найти. Это Contact Refinement Parallax Mapping?
Он просто делает несколько доп. итераций, чтобы найти лучшую точку?
Хотел еще Cone Mapping попробовать. Вроде у него сходимость быстрее.
AWPStar
Да , просто цикл с фикс шагом чтобы найти лучшее ... На резких переходах террейна не работае т деление на eye.z
У коне боанчинг внутри
AWPStar
> norm = texture(normalmap, cuv.xz / 128.0).xyz;
> tangent = vec3(1.0, 0.0, 0.0);
> bitangent = -cross(norm, tangent);
> tangent = cross(norm, bitangent);
> vec_proj= vec3( dot(viewDir, tangent), dot(viewDir, bitangent), dot(viewDir, norm) ).xzy;
> step_s = (layer_h/ float(layers)) /(-vec_proj.y);
А чего нормаль не приводится к 0..1? Нарисуй tangent normal в геометрическом шейдере бкдет понятно правильно ли задуман расчёт.
Andrey
ну да. но результата нормализация не меняет.
Просчет тангент спейса на каждой итерации дал значительное улучшение, но артефакты и искривление местами еще появляются. Попробую искусственно шаг уменьшить, может ошибка большая. Но думаю это только для проверки имеет смысл, т.к. сотню сэмплов на фрагмент делать - это конец.
Сомнительная касательная
Andrey
Нормали да, стоит проверить. Что-то мне показалось, что в местах, где идут искажения, направление сдвига сильно отличается от нормали. Вероятно тут, как минимум частично, из-за того, что у меня нормаль карты только xz хранит, а y восстанавливаю. Нет, видимо это я в другом месте делал
AWPStar
>Нормали да, стоит проверить. Что-то мне показалось, что в местах, где идут искажения, направление сдвига сильно отличается от нормали.
Нарисуй как это выглядит в геометрическом шейдере(сам так в Terrain сделал, прям опцией отладки показать TN) - так гадать тяжело и подойти к проблемным местам сразу удет понятно что не так. Потом тюнить менять шаги можно.