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

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

Страницы: 1 2 3 411 Следующая »
#15
(Правка: 12:10) 12:08, 2 дек. 2019

раб вакуумной лампы
> Не, ну ребят, я все конечно понимаю, только может вам уже настоящей математикой
> заняться а не этой фигнёй ? Там миллионы долларов уже десятки лет ждут тех, кто
> решит последние 9 загадок или как там было в истории с Перельманом. А
> рейтрейсеры это как то совсем мимо и геймдева и науки.
Как по мне, на высших уровнях технических наук дела обстоят как раз наоборот. Изучать только проблему Х и ничего лишнего - это самый верный способ не получить ничего вообще.
Чтобы осознать С++ и подчинить его своему разуму - нужно сначала научиться прогать на всех остальных языках. А всё потому, что 90% современных крестов - это заимствования; причём с полной потерей контекста, вырвиглазным синтаксисом и девятью слоями обратной совместимости. Разумеется, что удобнее сначала изучить эти фичи в их натуральной среде обитания и без тонн информационного шума, а потом уже с рабочим фундаментом разбирать их посреди крестовых тентаклей.
Так же и с физикой - нельзя просто взять и изучить квантовую электродинамику. Нужно сначала основательно разобраться как с самими физическими явлениями (что такое вообще электромагнетизм, почему Ленин - радиоволна и о чем СберДевил так до сих пор и не понял); так и с используемым математическим аппаратом - лагранжевой формулировкой, тензорным исчислением, теорией групп (ибо симметрии - это важно!) и прочими вагонами барахла.
А в математике, плот твист, не то же самое. Она ещё более связная.


#16
(Правка: 13:55) 13:52, 2 дек. 2019

Delfigamer
> что такое вообще электромагнетизм, почему Ленин - радиоволна
вообще-то Ленин — в первую очередь гриб, и только как следствие — электромагнитная волна.

> Чтобы осознать С++ и подчинить его своему разуму - нужно сначала научиться прогать на всех остальных языках.
что-то в этом есть, но мне кажется, всё ещё проще — надо просто прогать. хоть что. главное — чтобы ты этого не знал и оно было тебе интересно. по пути постоянно дотягивать то, что надо дотягивать.

#17
14:48, 4 дек. 2019

Линейная алгебра на коленках:

+ Показать

Необычность дизайна одна - вместо того, чтобы хранить компоненту w в рантайме, я разделяю векторы в компайл-тайме на Point (w=1) и Displacement (w=0).
Этому разделению можно дать и математическое обоснование. Displacement определяется как обычный 1-тензор - он не меняется при параллельном переносе координат. Point же, напротив, представляет собой абсолютную позицию точки, и поэтому зависит от выбора начала координат.
Если представить обычные единицы измерения как неуничтожимые множители (что довольно удобно и математически красиво); то Point так же содержит в себе неуничтожимое слагаемое, которое меняется при параллельном переносе.

#18
15:14, 4 дек. 2019

Suslik
> фишка референсных трейсеров на GPU в том, что можно брутфорсить вообще всё
> подряд и GPU вытянет просто за счёт нереальной вычислительной мощности.
> например, я рендерил радугу, просто моделируя рассеивание на микрокаплях воды,
> трассируя луч с френелем и внутренним переотражением внутри каждой капли,
> выпуская один луч на одну длину волны для моделирования дисперсии.
Так и свой CPU забрутфорсить до дурки недолго. Не каждая модель потянет.

#19
15:48, 4 дек. 2019

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

Begin PolyList
   Begin Polygon Item=OUTSIDE Texture=Blakm_d Flags=1073741824 Link=0
      Origin   -01600.000000,-00256.000000,+00256.000000
      Normal   +00000.000000,+00000.000000,+00001.000000
      TextureU +00001.000000,+00000.000000,+00000.000000
      TextureV +00000.000000,+00001.000000,+00000.000000
      Vertex   -00320.000000,-00192.000000,+00256.000000
      Vertex   -00320.000000,+00000.000000,+00256.000000
      Vertex   -00576.000000,+00000.000000,+00256.000000
      Vertex   -00576.000000,-00192.000000,+00256.000000
   End Polygon
   Begin Polygon Item=OUTSIDE Texture=A_Wal1a Flags=1073741824 Link=1
      Origin   -00288.000000,-00384.000000,+00000.000000
      Normal   -00001.000000,-00000.000000,-00000.000000
      TextureU +00000.000000,+00001.000000,+00000.000000
      TextureV -00000.000000,-00000.000000,-00001.000000
      Vertex   -00288.000000,-00384.000000,+00000.000000
      Vertex   -00288.000000,-00384.000000,+00048.000000
      Vertex   -00288.000000,-00128.000000,+00048.000000
      Vertex   -00288.000000,-00128.000000,+00000.000000
   End Polygon
   Begin Polygon Item=OUTSIDE Texture=A_Wal1a Flags=1073741824 Link=2
      Origin   +00157.197632,-00565.985840,+00000.000000
      Normal   +00000.812015,-00000.583636,+00000.000000
      TextureU -00000.583636,-00000.812015,-00000.000000
      TextureV -00000.000000,-00000.000000,-00001.000000
      Vertex   +00115.500000,-00624.000000,+00256.000000
      Vertex   +00115.500000,-00624.000000,+00000.000000
      Vertex   +00288.000000,-00384.000000,+00000.000000
      Vertex   +00288.000000,-00384.000000,+00256.000000
   End Polygon
...
Он же даже лучше, чем этот ваш обж - и геометрия пишется проще, и при этом на каждый полигон тут же натягивается текстура.
Пишем импортер.
+ Показать
Geometry::Face Geometry::BuildFace(FPoint const& a, FPoint const& b, FPoint const& c)
{
    FDisp ab = b - a;
    FDisp ac = c - a;
    FDisp an = norm(cross(ab, ac));
    FMat mwt;
    mwt[0] = ab.x; mwt[1] = ac.x; mwt[2] = an.x; mwt[3] = a.x;
    mwt[4] = ab.y; mwt[5] = ac.y; mwt[6] = an.y; mwt[7] = a.y;
    mwt[8] = ab.z; mwt[9] = ac.z; mwt[10] = an.z; mwt[11] = a.z;
    FMat mtw = inverse(mwt);
    FMat mww = mwt * mtw;
    mww[0] -= 1; mww[5] -= 1; mww[10] -= 1;
    float err = 0;
    for (int i = 0; i < 12; ++i) {
        err += mww[i] * mww[i];
    }
// стоп-точка в отладчике с условием err > 1.0e-3
    return Face{ mwt, mtw };
}
Разумеется, эта стоп-точка сработала, и не раз. Эти случаи можно разделить на категории:
1. Я не смог с первого раза правильно прописать все индексы в inverse(FMat). Исправлено, отлажено.
2. Кривое разбиение в Анриле, порождающее вырожденные треугольники. В самом АнрилЭнжине геометрия уровня собирается из CSG, и, как следствие, неаккуратно поставленный цилиндр может запросто разрезать ровный пол на кучу мелких полосок. Этот косяк исправляем в редакторе - "плохим" примитивам ставим флаг "semi-solid". Теперь движок сначала собирает уровень только из "хороших" solid-примитивов, а затем уже поверх этой геометрии накладывает semi-solid, при этом больше не трогая и не разрезая уже сформированную solid-геометрию.
+ иллюстрация

3. Плавающий питух в Анриле, порождающий вырожденные треугольники. С ним бороться невозможно. Можно лишь сосуществовать. Я не знаю, наверно, буду их просто выкидывать.

В конечном итоге, у меня получилось 1209 треугольников. Наверно, не так уж и плохо.

#20
17:14, 4 дек. 2019

А как проблему с шумом будешь решать?
Как по мне это самая главная проблема всех трейсеров. Деноизы не вывозят качество графики.

#21
(Правка: 19:01) 18:58, 4 дек. 2019

gamedevfor
> А как проблему с шумом будешь решать?
Никак, лол. Несмещённый трейсер обязан быть шумным; можно только менять форму этого шума, но избавиться от него вообще - невозможно в принципе.
_

Склеиваем модули в одно.
Доделываем эту волшебную функцию трейсинга:

struct TraceRequest
{
// input
    FPoint origin;
    FDisp dir;
// output
    float param;
    FPoint hit;
    Face* face;
};
bool Geometry::Trace(TraceRequest& tr)
{
    tr.param = INFINITY;
    tr.face = nullptr;
    for (Face& face : faces) {
        FPoint torigin = face.mtw * tr.origin;
        if (torigin.z <= 0) {
            continue;
        }
        FDisp tdir = face.mtw * tr.dir;
        if (tdir.z >= 0) {
            continue;
        }
        float tparam = - torigin.z / tdir.z;
        if (tparam >= tr.param) {
            continue;
        }
        FPoint thit = torigin + tparam * tdir;
        if (thit.x < 0 || thit.y < 0 || (thit.x + thit.y) > 1) {
            continue;
        }
        tr.param = tparam;
        tr.hit = tr.origin + tr.dir * tparam;
        tr.face = &face;
    }
    return isfinite(tr.param);
}
Делаем камеру:
struct Camera
{
    FMat mwc;
    float utan;
    float vtan;

    static Camera Targeted(FPoint const& origin, FPoint const& target, float utan, float vtan)
    {
        FDisp forward = norm(target - origin);
        FDisp up = FDisp{ 0, 0, 1 };
        FDisp right = norm(cross(forward, up));
        FDisp down = norm(cross(forward, right));
        Camera c;
        c.mwc[0] = right.x; c.mwc[1] = down.x; c.mwc[2] = forward.x; c.mwc[3] = origin.x;
        c.mwc[4] = right.y; c.mwc[5] = down.y; c.mwc[6] = forward.y; c.mwc[7] = origin.y;
        c.mwc[8] = right.z; c.mwc[9] = down.z; c.mwc[10] = forward.z; c.mwc[11] = origin.z;
        c.utan = utan;
        c.vtan = vtan;
        return c;
    }
};
Ну и наконец, выводим промежуточный результат на экран:
void RendererThread::ThreadFunc()
{
    Geometry geom;
    geom.LoadFromT3D("D:\\Program Files (x86)\\Unreal Tournament GOTY\\Maps\\CTF-Coret-FlagRoom.t3d");
    Camera camera = Camera::Targeted(
        FPoint{ 160, 228, 410 },
        FPoint{ -288, -384, 240 },
        1.0f * sqrtf(aspect), 1.0f / sqrtf(aspect));
    std::mt19937 rand;
    while (!rterminate.load(std::memory_order_relaxed)) {
        auto& bits = bitbuf.Back();
        bits.resize(width * height * 4);
        uint8_t* pixels = bits.data();
        for (int iy = 0; iy < height; ++iy) {
            uint8_t* line = pixels + iy * width * 4;
            for (int ix = 0; ix < width; ++ix) {
                uint8_t* pixel = line + ix * 4;
                float cx = 2.0f * (ix + 0.5f) / width - 1.0f;
                float cy = 1.0f - 2.0f * (iy + 0.5f) / height;
                TraceRequest tr;
                tr.origin = camera.mwc * FPoint{ 0, 0, 0 };
                tr.dir = camera.mwc * FDisp{ cx * camera.utan, cy * camera.vtan, 1.0f };
                if (geom.Trace(tr)) {
                    int color = tr.face->index;
                    pixel[0] = (color % 11) * 255 / 10;
                    pixel[1] = ((color / 11) % 11) * 255 / 10;
                    pixel[2] = ((color / 121) % 11) * 255 / 10;
                } else {
                    pixel[0] = 0;
                    pixel[1] = 0;
                    pixel[2] = 0;
                }
            }
        }
        bitbuf.Publish();
        InvalidateRgn(hwnd, nullptr, false);
    }
}
Координаты FPoint{ 160, 228, 410 }, FPoint{ -288, -384, 240 } я взял из редактора. Таким образом, я могу непосредственно сравнить расположение у Анрила и у меня и убедиться, что всё заимпортировано, настроено и отображено правильно.
Итак,
Изображение
Изображение
После небольшого исследования, обнаруживаем, что в Анриле ось Х направлена не вправо (как у меня), а влево.
Поправляем импортер (заменив x на -x), и пробуем ещё раз...
Изображение
А! Ещё надо поправить камеру, у неё ведь координаты тоже поменяются.
Изображение
Отлично! Чекпоинт достигнут.
А в следующей серии я наконец перейду к интегратору, и в сцене появится свет.

#22
(Правка: 19:14) 19:10, 4 дек. 2019

Я же правильно понимаю, что детишки любят картинки?
К тому же, это тред про рендеринг. А какой может быть рендеринг без картинок?

Вообще, по задумке, суть этого треда - именно в процессе, а не в результате. Чтобы обычные люди могли увидеть, хотя бы в общих чертах, как на самом деле chad developer приходит к успеху - начинаем с малого, добавляем фичи по очереди, решаем проблемы по мере поступления.

#23
19:31, 4 дек. 2019

Delfigamer
> Вообще, по задумке, суть этого треда - именно в процессе, а не в результате.

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

#24
20:11, 4 дек. 2019

gamedevfor

+ Показать

Цель состоит в том, чтобы на личном опыте изучить математику интегрирования по путям. Соответственно, самый эффективный способ достижения - это написать свой велосипед.
#25
20:21, 4 дек. 2019

Delfigamer
> Я же правильно понимаю, что детишки любят картинки?
Правильно.
Захожу смотреть на картинки, пока интересно.

А почему сцена такая странная?
Эти вырвиглазные цвета и сложность геометрии - дадут достаточно жуткую картину, тяжёлую к визуальной оценке.
Имхо, правильнее было бы что-то простое. Типа серые стены с цветным кубом внутри.

#26
20:36, 4 дек. 2019

Delfigamer
> Ну уж кому как не Гейм "Поверните Мне Шейдер" Девфору не знать, как работают
> профи
Мне нужен был результат, а здесь только процессом умеют заниматься - так что ничего удивительного.

Delfigamer
> Цель состоит в том, чтобы на личном опыте изучить математику интегрирования по
> путям.

Это называется криволинейный интеграл и этому учат на мехмате.

#27
21:16, 4 дек. 2019

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

122
> А почему сцена такая странная?
> Эти вырвиглазные цвета и сложность геометрии - дадут достаточно жуткую картину,
> тяжёлую к визуальной оценке.
Это - цветовая кодировка треугольников, и сделана исключительно для того, чтобы показать корректность импортированной геометрии, функции Trace и настройки "камеры". Разумеется, реальная картинка будет окрашена получше.
Да и вообще, мне кажется, у физически корректных рендеров уже само освещение выглядит красиво, даже если вся сцена чисто белая, так что за эстетику можно не беспокоиться.

122
> Имхо, правильнее было бы что-то простое. Типа серые стены с цветным кубом
> внутри.
А это слишком тривиально, так неинтересно.

#28
0:20, 5 дек. 2019

122
> А почему сцена такая странная?
> Эти вырвиглазные цвета и сложность геометрии …
Движoк к игре «Хищник» же! :-))
Delfigamer
Интересно!
А главное - так просто!
Но я в математике - НОЛЬ…
Даже не знаю, что где за что отвечает.
В ShaderToys провалился в тартарары…
Не по зубам…

#29
(Правка: 1:03) 0:59, 5 дек. 2019

Alikberov
> Но я в математике - НОЛЬ…
> Даже не знаю, что где за что отвечает.
Зря, зря. Потому что...
_

Математическая интермиссия!

Отражательную функцию я обозначил вот так:

    Изображение

Если материал изотропный, то вместо общих направлений Изображение можно обойтись тремя углами:

    Изображение - угол между нормалью к поверхности и направлением входящего луча;

    Изображение - угол между нормалью и направлением исходящего луча;

    Изображение - угол между плоскостями входящего и исходящего лучей.

Изображение

В таких обозначениях, функция Ламберта запишется так:

    Изображение

Отсюда, для нашей модели:

    Изображение

Назовём это распределение "ламбертовым". Как видно, оно зависит только от угла отражения.
Попробуем генерировать это распределение, выбирая независимо β и θ.
Очевидно, что угол отклонения θ распределен равномерно на промежутке [0; 2 π).
Найдём распределение угла отражения β:

    Изображение

Соответственно, кумулятивная функция распределения:

    Изображение

Наконец, выразим искомый β через параметр q:

    Изображение

Альтернативно, можем рассмотреть распределение Изображение:

    Изображение

    Изображение

Неожиданно просто!
Значит, алгоритм генерации ламбертова распределения можно выбрать таким:
1. Получив вектор нормали n, достраиваем его до ортонормального базиса, выбрав произвольным образом два перпендикулярных ему и друг другу вектора b и t.
2. Генерируем канонически распределённое q.
3. На его основе, вычисляем:

    Изображение

    Изображение

4. Генерируем координаты равномерной единичной окружности {u, v}.
5. Возвращаем вектор:

    Изображение

Страницы: 1 2 3 411 Следующая »
ФлеймФорумПроЭкты