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

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

Страницы: 16 7 8 9 10 11 Следующая »
#120
(Правка: 4:02) 4:01, 12 янв. 2020

Ага! Вот теперь я нашёл проблему.
Чтобы упростить вычисления, я свёл источник света к одной точке.
Затем, чтобы замерить фактическую плотность распределения, я подменил значение вклада:

    //RecordContribution(dir, (float)(weight * flux / dens));
    RecordContribution(dir, 0.1f);
Если рендерить от источника - то во фреймбуфере должно отложиться некое подобие корректно освещённой картинки; поскольку, как было показано на предыдущих страницах, от настоящего flux / dens вклад будет отличаться только парой коэффициентов, относящихся к камере.
Изображение
Если же хотя бы 2 крайних вертекса взяты от камеры - то тогда, поскольку направление последнего отрезка распределяется равномерно по плоскости экрана, то и во фреймбуфере должен отложиться ровный белый свет.
Однако же, по факту, для путей 1+2 получается вот такое чудо технологии:
Изображение
После недолгих поисков, выясняется, что за волшебный узор отвечает тест видимости:
    if (!lightsection.empty()) {
        float geom = VisibleGeometric(*lightsection.back(), *lenssection.back());
        if (geom <= 0) {
            return;
        }
    }
Если закомментировать этот тест, то картинка превращается в монотонный серый квадрат, как и ожидалось.
Дело в том, что при соединении кусков путей, может получиться такой путь, где ребро-соединитель проходит сквозь геометрию. Разумеется, по таким путям свет проходить не может, следовательно, никакого вклада от такого пути быть не должно.
Проблема оказалась в том, что, когда тест видимости проводится на отрезке, конец которого лежит на твёрдой поверхности, то из-за округлений отрезок то считается пересекающим эту самую поверхность, то чуть-чуть недотягивающимся до неё - отсюда и волшебный узор.
Короче, плавающий питух возвращается.
Добавив небольшой хвостик в функцию теста видимости:
        float tparam = -toz / tdz;
        //if (tparam >= 1) {
        if (tparam >= 0.999f) {
            continue;
        }
от видимых волшебных узоров удалось таки избавиться:
Изображение
На такой картинке виден оставшися узор на треугольнике, лежащим в одной плоскости с концом тестируемого отрезка - это не страшно, в дальнейших вычислениях вклад от этих путей убьёт косинус практически прямого угла, поэтому о них можно не беспокоиться.


#121
6:43, 14 янв. 2020

Ну вот, итоговый вариант - полный двусторонний трейсер; по тем же правилам - начинаем с 1 комбинированного сэмпла на 10 пикселов, и каждый кадр удваиваеваем.
Изображение

+ итог

И, для желающих сравнить результаты для малых spp - камерный трейсер, световой и двусторонний в виде развёрнутых альбомов вместо анимаций.

#122
13:33, 14 янв. 2020

Delfigamer
> Ну вот, итоговый вариант - полный двусторонний трейсер; по тем же правилам -
> начинаем с 1 комбинированного сэмпла на 10 пикселов, и каждый кадр
> удваиваеваем.
красиво, что дальше?
лайтмаппер из него сделаешь?

#123
14:58, 14 янв. 2020

Misanthrope
Буду ускорять и прикручивать материалы.

#124
12:11, 15 янв. 2020

А нет ли какой то более удобной формы вот такого "репортажа". Ну скажем код херачить в гитхабе, где  видно всю историю изменений. Текст и выкладки тоже где то, и тоже версионировать. А на форуме ссылки на "серии", коими могут быть комиты, бранчи.. Фигачить весь контент на форуме как то не айс.

#125
14:22, 15 янв. 2020

а по-моему, нормально. код меня не интересует, а за прогрессом можно последить. только на CPU слишком просто.

#126
23:28, 15 янв. 2020

Почитал..чет воды много, мяса мало. Изюм  рейтресеров - провести кучу лучиков через кучу пространства и поверхностей. А тут такое.. winapi, Ламберт.. несериезна эта.

Позовите MsShoor, пусть про триангуляцию на GPU расскажет.

#127
5:37, 16 янв. 2020

slepov
> Почитал..чет воды много, мяса мало. Изюм рейтресеров - провести кучу лучиков
> через кучу пространства и поверхностей. А тут такое.. winapi, Ламберт..
> несериезна эта.
Так куча лучиков уже давно описана в ПЕЙПЕРЕ. Не заниматься же тупо переводом ПЕЙПЕРА на русский язык, в самом деле.

#128
(Правка: 4:16) 4:04, 17 янв. 2020

Перевожу сэмплер с расчёта величин PathFlux/PathDensity по геометрии пути - на расчёт PathContribution/PathWeight по заранее подготовленным коэффициентам.
За счёт того, что можно было сравнить числа не просто для всей картинки, а для каждого конкретного сэмпла, добиться равнозначности было несложно.

+ Показать

(всё тупо по тому же ПЕЙПЕРУ, кстати говоря)
Однако, появилась новая проблема - иногда, во фреймбуфер стали попадать NaN и портить всю картинку.
Причина - иногда появляются рёбра с Изображение равным нулю. Из-за этого, e.pathdensfactor = e.geom * prev.scatterdens * prev.passrate также оказывался нулевым, попадал в таком виде в знаменатель при вычислении PathWeight и пакостил.
Более глубокая причина - GenerateLambertian изредка генерировал лучи, направленные строго перпендикулярно нормали. Как следствие, Изображение для такого переотражения вырождался в ноль, от него и поехало.
Предполагаемое решение - в GenerateLambertian вместо
    float q;
    GenerateUniform(q);
    float z = sqrtf(1.0f - q);
    float r = sqrtf(q);
брать
    float q;
    GenerateUniform(q);
    float z = sqrtf(q);
    float r = sqrtf(1.0f - q);
Фишка - q может быть иногда равным нулю, но никогда не может стать единицей. В первом варианте - лучи иногда попадают на 90° от нормали, но никогда - на саму нормаль. Во втором - наоборот, иногда будут стрелять прямо в нормаль и никогда - строго вдоль плоскости.

Да, а средняя частота пускания таких прижатых лучей - около 10-7 вызовов GenerateLabmertian (посчитано через 800*600 сэмплов * 4 кадра * 8 лучей на сэмпл), что примерно соответствует частоте нуля в выдаче 24-битного эрэнджи. Что соответствует битности мантиссы float, как и назначено Стандартом. Видите, у меня даже баги сходятся!
_

Ну и конечно же, отладка 90 лвл.

    if (isnan(contribution)) {
        throw "shit";
    }
    if (isnan(weight)) {
        throw "damnit";
    }

#129
4:30, 17 янв. 2020

Разумеется, ловчая сеть опять сработала, на этот раз - на "дерьме".
При генерации светового пути, в первом же узле откладывается e.contribution == inf (который, судя по всему, потом где-то умножается на ноль). А всё потому, что генератор:

        int ei = 0;
        float q;
        GenerateUniform(q);
        while (ei < (int)emitters.size() - 1 && q >= emitters[ei].fraction) {
            q -= emitters[ei].fraction;
            ei += 1;
        }
иногда проскакивает через все полигоны и тормозит на последнем.
А последний полигон - нифига не светящийся, поэтому у него плотность выставлена emitters[ei].dens == 0, и из-за этого вклад уходит в небеса.
        while (true) {
            float q;
            GenerateUniform(q);
            while (ei < (int)emitters.size() - 1 && q > emitters[ei].fraction) {
                q -= emitters[ei].fraction;
                ei += 1;
            }
            if (emitters[ei].fraction != 0) {
                break;
            }
        }

#130
12:15, 19 янв. 2020

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

Дополнительно, реализовал подсчёт погрешности вычисления - вместо аккумуляции всего света в одном фреймбуфере, сэмплы случайным образом раскидываются между 4-я буферами:

void SamplerBase::RecordToFrame(int x, int y, float value)
{
    auto dist = std::uniform_int_distribution<int>{ 0, FrameCount - 1 };
    int p = dist(rand);
    if (x >= 0 && x < width && y >= 0 && y < height) {
        frames[p][y * width + x] += value;
    }
}
При выводе - свет со всех буферов складывается вместе и получается результат, идентичный прежнему.

При вычислении погрешности же, показания во фреймбуферах считаются как 4 разные оценки интеграла. Для каждого пиксела, мы находим дисперсию этих оценок; затем собираем среднее квадратичное по всему изображению:

SamplerBase::PerfInfo const& SamplerBase::GetPerfInfo()
{
    perf.traceTime = traceperf.Time();
    if (denominator > 0) {
        double errorsqr = 0;
        for (int i = 0; i < width * height; ++i) {
            double sum = 0;
            double sumsqr = 0;
            for (int p = 0; p < FrameCount; ++p) {
                double value = frames[p][i] / denominator;
                sum += value;
                sumsqr += value * value;
            }
            double avg = sum;
            double avgsqr = sumsqr * FrameCount;
            double var = avgsqr - avg * avg;
            errorsqr += var;
        }
        double rms = sqrt(errorsqr / (width * height));
        perf.error = (float)rms;
    } else {
        perf.error = 0;
    }
    return perf;
}
Корректность коэффициентов оценим такой прикидкой.
Возьмём, например, картинку с 27spp:
Изображение
Как видно, алгоритм утверждает, будто средняя погрешность по пикселам - около 0.06.
При "экспорте" полученного результата, изображение делится на блоки по 100*100 пиксел, и в текстовый файл записываются средние значения по блокам. Таким образом, там на каждое число будет приходиться в 100² больше сэмплов, чем на пиксель, значит, погрешность будет в 100 раз меньше - порядка 0.0006.
По факту получаем:
     ref        img        delta
   0.147215   0.147651  -0.000436
   0.190855   0.191012  -0.000157
   0.214440   0.214639  -0.000199
   0.220646   0.221070  -0.000424
   0.219323   0.219805  -0.000482
   0.215793   0.215291   0.000502
Критических расхождений нет, всё чётко в практических пределах мутности.

#131
15:05, 19 янв. 2020

Приделал автоматическое сохранение кадров через libpng.
И научил рендерер работать с цветом через FDisp.
Изображение
Кажется, надо немного переделать тонмаппер, и у меня даже есть идея, как.

#132
15:41, 21 янв. 2020

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

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

+ Показать

Проблемное место - когда соединитель оказывается под прямым углом к оси камеры. Cos[a] в таком случае закономерно оказывается нулевым, что даёт нулевой Изображение и одновременно бесконечный Изображение.
Решение - не принимать лучи с боков и задницы камеры:
float BidirSampler::ScatterDensity(Event const& e, FDisp indir, FDisp outdir)
{
    switch (e.cat) {
    case EventCat::Emission:
        return invpi;

    case EventCat::Scatter:
        return invpi;

    case EventCat::Sensor:
    {
        float cosa = dot(indir, outdir);
        if (cosa <= 0) { // new
            return 0;    // new
        }                // new
        float cosasqr = cosa * cosa;
        float cosabisqr = cosasqr * cosasqr;
        return 1.0f / (4.0f * camera.utan * camera.vtan * cosabisqr);
    }
    }
    return 0;
}

#133
15:47, 21 янв. 2020

Delfigamer
> Решение - не принимать лучи с боков и задницы камеры:

Ну это не решение, а костыль. )))

#134
16:01, 21 янв. 2020

gamedevfor
> Ну это не решение, а костыль. )))
Не-а, всё 100% чётко - функция сенсора действительно сходит на ноль, когда луч уходит за пределы угла обзора камеры.
Было бы ещё более корректно прямо в этой функции вычислять u и v, и проверять типа

if (u < -camera.utan || u > camera.utan || v < -camera.vtan || v > camera.vtan) {
    return 0;
}
но в этом нет смысла, потому что по сути та же самая проверка в любом случае происходит дальше по коду, где из u и v вычисляются координаты пиксела во фреймбуфере.
В принципе, единственная проблема нанов такого вида - это то, что они взрвывают специально поставленную мину. Даже если бы он прошёл дальше, на этапе определения координат пиксела такой сэмпл бы всё равно отбросился.
Страницы: 16 7 8 9 10 11 Следующая »
ФлеймФорумПроЭкты