вторник, 14 июля 2009 г.

Raymarchng with distance fields. Просто о простом
Аффтар: Madware.ru
-----------------------------------------------------------------------------
В этой статье я объясню что такое raymarching, когда он нужен и как его надо употреблять в пищу.

Raymarching - разновидность техники трассировки лучей, которая заключается в брутфорсовом методе нахождения пересечения фигуры и луча. То есть у нас есть луч который определяется через формулу Position = RayDirection * t. На первом шаге t = 0; и мы с каждым шагом прибавляем к t достаточно малую величину dt. Все это повторяется до тех пор пока отрезок между
RayDirection *t и RayDirection *(t+dt) не пересечет поверхность фигуры или не уйдет на определенное расстояние. В случае пересечения мы получаем позицию пересечения Position = RayDir *(t + dt/2).

Этот метод применим к фигурам, которые не имеют аналитической формулы для нахождения координат пересечения. Вот как для того блоба, того что на рисунке.

Для него мы не знаем точной формулы координат пересечения, но зато точно знаем условие пересечения. Блоб задается формулой, позаимствованной у физиков, гласящей что потенциал в точке пространства Pos будет равен сумме потенциалов от всех источников потенциала. Обычно эти потенциалы являют собой обратное значение квадрата расстояния от Pos до Pi, где Pi - позиция источника потенциала. То есть мы получим такую штуку:
P = сумма от 1 до n 1/(dot(Pos - Pi,Pos - Pi))
Тут поясним - так как Pos и Pi - векторы и расстояние равно квадрату квадратного корня из суммы квадратов координат по x,y и z(а это не что иное как скалярное произведение), то мы просто делим 1 на скалярное произведение вектора Pos - Pi с самим собой.
Так вот: при P < какой-то заданной константы C точка находитя за предами блоба, в случае же когда P > C точка внутри. Вот мы и получили условие пересечения. Похожим образом можно поступать и с другими фигурами заданными такой функцией. О них говорится в gpu gems, раздел о технике marching cubes.

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

А так же техника distance fields, которую мы и рассмотрим.

Хорошее объяснение дано замечательным демокодером iq в этой презентации:
Rendering Worlds With Two Triangles

Суть оптимизации в том, что мы каждый раз выбираем dt, исходя из минимальной дистанции от точки Pos до нашей фигуры. Так мы экономим уйму времени, которое тратится в холостую, но стоит учесть что в таком случае нужно ограничить число проходов цикла, так как dt будет стремиться к нулю в случае бликого прохождения луча от фигуры.

Сейчас я приведу мои шейдеры для реймарчинга блоба, зависящего от трех источников потенциала.

Вершинный шейдер


Здесь мы не делаем ничего кроме того, что получаем истинные координаты источников потенциала.

Фрагментный шейдер намного больше


Функция Blob3 определяет потенцал точки Pos;
CalcBlob3Normal вычисляет номаль к этой точке;
CalcBlob3Light отвечает за освещение, в данном случае это точечный источник + спекуляр;
CalcBlob3Light выполняет основную работу по нахождению точки пересечения.
Строки
pp1 = p1 - p;
t = length(pp1)-rad;
pp1 = p2 - p;
len = length(pp1)-rad;
if(len < t =" len;" pp1 =" p3" len =" length(pp1)-rad;" t =" len;">выражают основной смысл метода нахождения наименьшего расстояния
Здесь нужно заметить что мной было использовано такое свойство блоба, что C сравнимо с 1/r^2, где r - "радиус шара". На самом деле C немного больше, но для нашего метода это не помеха.
В строках
c1 = Blob3(p,p1,p2,p3);
pp = p + rd * t;
c2 = Blob3(pp,p1,p2,p3);
мы находим потенциалы на концах отрезка, которые потом будем сравнивать с C.
В цикле мы кждый раз проверяем не пересекает ли отрезок нашу фигуру, не ушел ли луч в бесконечность, не превышен ли лимит итераций. В двух последних случаях рисуем фон, в противном случае выставляем dt равным одной сотой от t и в цикле ищем наименьшее отклонение от потенциала C. Заметим, что как только отклониние становится большим, чем наш минимум, имеет смысл прекратить цикл.
Строки
p = p + rd * mint;
return CalcBlob3Light(p,p1,p2,p3,LightPos,color);
находят результирующий цвет точки экрана.

gl_FragColor=(SimpleBlob3D(v.x - dPos,v.y,Pos1,Pos2,Pos3) + SimpleBlob3D(v.x + dPos,v.y,Pos1,Pos2,Pos3)+ + SimpleBlob3D(v.x,v.y - dPos,Pos1,Pos2,Pos3) + SimpleBlob3D(v.x,v.y+dPos,Pos1,Pos2,Pos3))/4.0;
Здесь мы находим среднее значение для четырех точек, соседних истинной, и получаем на выходе гладкую картинку.

Вот в общем-то и все :)

1 комментарий:

  1. Блин... Баги какие-то
    Неправильно вставились букавке вот тут:
    pp1 = p1 - p;
    t = length(pp1)-rad;
    pp1 = p2 - p;
    len = length(pp1)-rad;
    if(len < t =" len;" pp1 =" p3" len =" length(pp1)-rad;" t ="

    правильно так:
    pp1 = p1 - p;
    t = length(pp1)-rad;
    pp1 = p2 - p;
    len = length(pp1)-rad;
    if(len < t) t = len;
    pp1 = p3 - p;
    len = length(pp1)-rad;
    if(len < t) t = len;

    и внизу тоже баг:
    "CalcBlob3Light выполняет основную"
    заменить на
    "SimpleBlob3D выполняет основную "

    ОтветитьУдалить