Войти
ФлеймФорумПроЭкты

Я пишу несмещной пат-трейсер (11 стр)

Страницы: 16 7 8 9 10 11
#150
6:17, 25 янв. 2020

512 spp
Изображение
Текстура с "заклёпками" смотрится так по-дурацки, лол.
И что это за хреновина такая:
Изображение
Это типа тень? Выглядит странно.
Изображение
Ну да, похоже на тень от той лампы, что на первом этаже по центру. Но всё равно выглядит странно.


#151
12:02, 25 янв. 2020

MrShoor
> Подхачить UV координаты так, чтобы первая производная не ломалась (при линейной
> интерпояции), и оставить билинейную интерполяцию:
> https://iquilezles.org/www/articles/texture/texture.htm

    Изображение

Изображение

    Изображение

Изображение
#152
6:05, 28 янв. 2020

Мелкие заметки.

Отражение можно реализовать как поворот на 180° вокруг нормали.
Формулу поворота скачаем отсюда:

    Изображение

    Изображение

Чтобы развернуть вектор вокруг нормали - нужно повернуть его... этой же нормалью? Окей.

    Изображение

    Изображение

Нужно умножить вектор на нормаль внешне, затем внутренне, и плюс вычесть единицу.
Будучи расписанной в таком виде, формула выглядит как-то по-глупому просто.

Хотя конечно же найдётся хоть один человек, который и после такого не сможет отразить луч от зеркала и попросит готовый код на glsl.

#153
10:12, 28 янв. 2020

Delfigamer
Не выпендривайся с фильтрацией. Лучше возьми PBR материалы с текстурами высокого разрешения и используй для них обычную билинейную фильтрацию.

#154
(Правка: 0:09) 0:03, 1 фев. 2020

Panzerschrek[CN]
> Не выпендривайся с фильтрацией. Лучше возьми PBR материалы с текстурами
> высокого разрешения и используй для них обычную билинейную фильтрацию.
Фильтрация это просто и быстро. Раз нет веских причин от неё отказываться, то можно и сделать.
_

А вот путевой интегратор - это сложно. В процессе подготовки интегратора к зеркалам несколько поменял код, и в итоге стал ловить фигню на чисто ламбертовых сценах.
Исправил, конечно. Вот какие тонкости у меня вышли.
1. Чтобы проверить видимость между двумя точками (когда две несвязанные точки стягиваются соединителем), недостаточно просто проверить видимость между двумя точками. Нужно ещё убедиться, что на обоих концах соединитель приходит на лицевую сторону поверхности:

FDisp BidirSampler::PathContribution(int lightnodes, int lensnodes)
{
    FDisp rb;
    if (lightnodes == 0) {
        Event& lense = lenspath[lensnodes - 1];
        if (DeltaOrder(lense) != 0) {
            return FDisp{ 0, 0, 0 };
        }
        rb = lense.contribution;
        rb *= ScatterFlux(lense, lense.norm, lense.outdir);
    } else {
        Event& lighte = lightpath[lightnodes - 1];
        Event& lense = lenspath[lensnodes - 1];
        if (DeltaOrder(lighte) + DeltaOrder(lense) != 0) {
            return FDisp{ 0, 0, 0 };
        }
        FDisp conndir = norm(lense.origin - lighte.origin);
        // вот эта проверка:
        if (dot(lighte.norm, conndir) <= 0 || dot(lense.norm, -conndir) <= 0) {
            return FDisp{ 0, 0, 0 };
        }
        rb = lighte.contribution * lense.contribution;
        rb *= ScatterFlux(lighte, lighte.indir, conndir);
        rb *= Geometric(lighte, lense, conndir);
        rb *= ScatterFlux(lense, lense.indir, -conndir);
    }
    return rb;
}
Если так не делать, то во фреймбуфер станут откладываться лишние сэмплы. Иногда - с положительным знаком, иногда - с отрицательным. У меня с такой ошибкой числа получались выше, чем на референсе. Скорее всего, лишние сэмплы - это когда соединитель проходил целиком внутри геометрии, например - стягивал две точки на разных сторонах центральной колонны. Тогда, с точки зрения рейкаста, обе поверхности оказываются в мёртвой зоне проверяемого луча, а больше никакие треугольники его и не пересекают, вот он и выдавал "никаких пересечений не найдено".

2. Когда мы считаем вклад от путей с нулём световых вершин, то последнюю вершину сенсорного пути нужно временно переквалифицировать из "диффузного отражения" в "источник света" - чтобы вместо BRDF функция ScatterFlux выдала плотность излучения, и плотности вероятности посчитались как для источника. До сих пор, эта временная переквалификация происходила в PathContribution:

FDisp BidirSampler::PathContribution(int lightnodes, int lensnodes)
{
    FDisp rb;
    if (lightnodes == 0) {
        Event& lense = lenspath[lensnodes - 1];
        EventCat oldcat = lense.cat;
        lense.cat = EventCat::Emit;
        rb = lense.contribution;
        rb *= ScatterFlux(lense, lense.norm, lense.outdir);
        lense.cat = oldcat;
    } else {
        <...>
    }
    return rb;
}
Разумеется, в PathWeight такй же манипуляции не происходило, из-за чего вес таких сэмплов считался неправильно, а как следствие - ехали и вклады, и итоговые значения в картинке.
Решил тем, что перенёс операцию на уровень выше:
void BidirSampler::RecordPathContribution(int lightnodes, int lensnodes)
{
    if (lightnodes < 0 || lensnodes <= 0 || lightnodes + lensnodes < 2) {
        return;
    }
    if (!PathEnabled(lightnodes, lensnodes)) {
        return;
    }
    // вот здесь:
    EventCat lensecat = lenspath[lensnodes - 1].cat;
    Finally lensecatrestorer = { [&]()
        {
            lenspath[lensnodes - 1].cat = lensecat;
        } };
    if (lightnodes == 0) {
        lenspath[lensnodes - 1].cat = EventCat::Emit;
    }
    FDisp contribution = PathContribution(lightnodes, lensnodes);
    float cluma = luma(contribution);
    if (cluma == 0) {
        return;
    }
    float weight = PathWeight(lightnodes, lensnodes);
    if (isnan(cluma)) {
        DumpPath(lightnodes, lensnodes);
        throw "shit";
    }
    if (isnan(weight)) {
        DumpPath(lightnodes, lensnodes);
        throw "damnit";
    }
    contribution *= weight;
    FDisp lensdir;
    if (lensnodes >= 2) {
        lensdir = lenspath[0].origin - lenspath[1].origin;
    } else {
        lensdir = lenspath[0].origin - lightpath[lightnodes - 1].origin;
    }
    if (lightnodes != 0 && lensnodes != 0) {
        if (!TestVisible(lightpath[lightnodes - 1].origin, lenspath[lensnodes - 1].origin)) {
            return;
        }
    }
    RecordContribution(lensdir, contribution);
}
И, как побочный эффект, сделал велосипедный Finally - чтобы не копипастить строчку с восстановлением после каждого раннего выхода из процедуры.
_

Тем временем, я вернул в оборот простой ForwardTracer, научил его работать с текстурами и добавил к нему отражения. Как обычно, код максимально простой:

void ForwardSampler::IteratePixel(int ix, int iy)
{
    float dx;
    float dy;
    GenerateTriangle(dx);
    GenerateTriangle(dy);
    float cx = 2.0f * (ix + dx + 0.5f) / width - 1.0f;
    float cy = 1.0f - 2.0f * (iy + dy + 0.5f) / height;
    float cu = cx * camera.utan;
    float cv = cy * camera.vtan;
    TraceRequest tr;
    tr.origin = camera.mwc.origin();
    tr.dir = norm(camera.mwc * FDisp{ cu, cv, 1.0f });
    FDisp current = FDisp{ 1, 1, 1 };
    int pathlen = 1;
    while (Trace(tr)) {
        pathlen += 1;
        float tu = dot(tr.hit, scene[tr.face].texu);
        float tv = dot(tr.hit, scene[tr.face].texv);
        //if (pathlen == 4) {
            FDisp emitcolor = scene.SampleColor(scene[tr.face].emittex, tu, tv) * scene[tr.face].emitgain;
            RecordToFrame(ix, iy, current * emitcolor);
        //    break;
        //}
        FDisp normal = scene[tr.face].mwt.zunit();
        FDisp reflectcolor = scene.SampleColor(scene[tr.face].reflecttex, tu, tv);
        float reflectluma = luma(reflectcolor);
        if (RandomBool(reflectluma)) {
            current *= reflectcolor / reflectluma;
            tr.origin = tr.hit;
            tr.dir = reflection(normal, -tr.dir);
        } else {
            FDisp diffusecolor = scene.SampleColor(scene[tr.face].diffusetex, tu, tv);
            diffusecolor *= FDisp{ 1, 1, 1 } - reflectcolor;
            diffusecolor /= 1 - reflectluma;
            float diffuseluma = luma(diffusecolor);
            if (RandomBool(diffuseluma)) {
                current *= diffusecolor / diffuseluma;
                tr.origin = tr.hit;
                GenerateLambertian(normal, tr.dir);
            } else {
                break;
            }
        }
    }
}
Подобно тому, как emit и diffuse у меня - это два независимых параметра одного и того же универсального материала, reflect добавляется третьим параметром во всё тот же материал.
Итоговая BRDF при этом определяется как линейная комбинация дискретных вариантов:

    Изображение

При этом, чтобы гарантировать сохранение энергии, коэффициенты R и D не просто берутся из текстуры, а модулируются:

    Изображение

    Изображение

В дальнейшем, диффузное и спекулярное пропускание планируется добавлять как дополнительные "слои" после отражения.

Опять же, поскольку большинство реальных материалов либо зеркальные, либо нет - то на практике такая модель будет вырождаться в обычные материалы - чисто светящиеся, чисто матовые, чисто преломляющие и так далее.
_

Тем временем, текущее состояние референсного рендера:

Изображение

Здесь на стены, пол и потолок натянута текстура R:

    Изображение

где квадраты закрашены цветами (0.8, 0.0, 0.0); (0.8, 0.8, 0.8) и (0.0, 0.0, 0.0) с учётом гамма-кодирования.
Под ней расположена текстура D, залитая однотонным (0.8, 0.8, 0.8).

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

#155
8:01, 2 фев. 2020

Референс: 16 kspp, односторонний трейсер.
Изображение

И, конечно же, с первого раза ничего не сошлось, так что вот референс по частям.
Вклад от путей с тремя вершинами:
Изображение
С четыремя:
Изображение
С пятью:
Изображение

#156
(Правка: 8:34) 8:33, 2 фев. 2020

конечно, любая сохраняющая энергию BRDF, в общем-то, физична. однако, я бы советовал не заниматься хернёй и взять нормальную BRDF вроде GGX, которая является де-факто стандартом в индустрии, покрывает 99.9% существующих материалов, для неё миллион ассетов для тестирования и куча вспомогательной математики вроде importance sampling'а. ты сам-то в жизни где-то видел свои красно-зелёные полоски без спекуляра?

#157
9:56, 2 фев. 2020

Suslik
> ты сам-то в жизни где-то видел свои красно-зелёные полоски без спекуляра?
Лакированные поверхности, например - зеркало сверху, диффуз под ним.
Или грязное стекло - что не отразилось, уходит на другую сторону.
Там самая главная фича - это способность работать с brdf, где конечная плотность складывается с дельта-функцией. Остальное пока держится на условно-подстановочном уровне.

#158
(Правка: 11:18) 11:15, 2 фев. 2020

Delfigamer
> Лакированные поверхности, например - зеркало сверху, диффуз под ним.
> Или грязное стекло - что не отразилось, уходит на другую сторону.
такие поверхности называются диэлектрическими зеркалами, их очень трудно получить, они стоят бешенных денег и в обиходе практически не встречаются за исключением специального покрытия линз в фотоаппаратах. поэтому практически все материалы, которые встречаются, на просвет имеют такой же цвет, как на отражение, потому что цвет диэлектриков определяется диффузным цветом и он имеет одинаковый вклад что на просвет, что на подповерхностное рассеивание. а отражение от поверхности для диэлектриков — это практически всегда функция, которая вообще не зависит от длины волны и поэтому спекуляр у практически всех реальных объектов белый. меняется только микрорельеф поверхности, который определяет форму спекуляр части brdf, а не её спектральный состав.

исключения же составляют всякие хитрые поверхности вроде labmda/2 оптических пластинок, крыльев бабочек и двулучепреломляющих сред. вот это вот ты, собственно, и намоделировал — всё то, что в жизни почти не встречается.

#159
(Правка: 10:47) 10:35, 3 фев. 2020

О, я придумал отговорку против пбр - "я художник я так вижу".

А вообще, я стараюсь вводить изменения по минимуму. Сначала - чистый ламберт, отлаживаем до состояния корректности (которое выражается в несмещённости и проверяется сходимостью разных техник к одному результату). Потом - ламберт с текстурами. Потом - ламберт и зеркала.
После зеркал я планирую научить лучи проходить на другую сторону. Потом - терять энергию в средах.
После этого можно будет ввести нормал-маппинг. И нетривиальные btdf. И спекулярные источники света - с дельта-функциями в плотностях. И даже тун-шейдер. Я хочу реализовать такую brdf, чтобы результат выглядел как аниме.

#160
15:29, 3 фев. 2020

Delfigamer
> Потом - ламберт и зеркала.
Идеальные зеркала — это шаг в сторону от нормальной BRDF.
Если у тебя поддерживаются спекуляры произвольной ширины, то реализация зеркал на их основе тривиальна.
Обратное же — неверно, поэтому реализация зеркал — это бесполезная трата времени.

#161
15:31, 3 фев. 2020

Delfigamer
> И даже тун-шейдер. Я хочу реализовать такую brdf, чтобы результат выглядел как аниме
тун-шейдер не через brdf делается а через постпроцесс. потому что сами поверхности переотражают свет между друг другом не дискретизованный, это бы выглядело очень странно, дискретизация происходит в самом конце.

#162
(Правка: 8:24) 8:23, 22 фев. 2020

 Изображение Мне следует сделать спектральную цветовую модель.

Может, есть интересные статьи о том, как по-хорошему такое делается с использованием обычных rgb-текстур?

В общем, на текущий момент моя мысль летит вот в таком направлении.

С помощью математического пакета, по отбалдынскому алгоритму генерируется несколько базовых спектров для опорных цветов - {1, 0, 0}, {1/2, 1/2, 0} и прочие вореции.
Затем, уже на стороне рейтрейсера, встретив в эмиссивной текстуре цвет {r, g, b}, путём линейной интерполяции трейсер генерирует спектр для хромы {r, b} / (r+g+b) и затем масштабирует его до итогового цвета.

Опорные точки я расположил в равномерную треугольную сетку между {1, 0, 0}, {0, 1, 0} и {0, 0, 1}:

Изображение

Получились вот такие спектры излучения:

+ Показать

Что до отражателей, то думаю сделать так. В качестве "цвета" отражателя будем считать цвет спектра, полученного покомпонентным умножением референсного излучения (скачанный из интернета D65 illuminant) на отражательную функцию. Раз человек, по слухам, более чувствителен к яркости, чем к окраске, то при синтезе опорных функций будем в первую очередь подгонять компоненту Y отражённого света, а хрому уже как получится при условии, что функция отражения лежит между 0 и 1.

#163
(Правка: 9:47) 9:41, 25 фев. 2020

Долго думал, как же именно алгоритмом посчитать отражательные функции.
В итоге вместо ручного педалирования таки смог сформулировать проблему для встроенного оптимизатора:

    Изображение

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

Страницы: 16 7 8 9 10 11
ФлеймФорумПроЭкты