Уменьшение временной сложности O(n²) алгоритма Ray Casting

Я написал алгоритм приведения лучей на основе стандартных алгоритмов. Точка пересечения вычисляется с помощью алгоритма Меллера-Трумбора (который сократил время выполнения примерно на 350 % по сравнению с более простым алгоритмом).

В целом выполняются следующие шаги:

  1. Для каждого треугольника выстрелите лучом от источника света к треугольнику.
  2. Проверьте, есть ли другие треугольники, которые пересекаются с лучом. Получите тот, у которого минимальное расстояние до источника луча (т.е. источника света).
  3. Осветите тот, который находится на минимальном расстоянии (установите затененный = false для треугольника)

Мне не нужны разные вариации штриховки; треугольники должны содержать информацию только в том случае, если они вообще затенены или нет (логическое значение).

Проблема в том, что для шага 2 мне нужно выполнить проверку пересечения для всех треугольников в сцене. Другими словами, временная сложность равна O(n²). Однако я читал, что можно использовать алгоритм луча с временной сложностью O (log n).

У меня есть несколько идей по сокращению времени выполнения. Например, я мог бы исключить из расчета все треугольники с большим расстоянием до источника света, чем тот, на который направлен луч, что могло бы сократить время выполнения на 50 %. Но сложность по-прежнему будет O(n²), и это не сильно поможет при обработке больших объемов данных.

Например, использование алгоритма трассировки лучей на сцене с числом 100 000 по-прежнему возможно, но занимает около 10 минут, и это количество будет экспоненциально увеличиваться, когда сцена состоит из большего количества треугольников.

Есть ли способ уменьшить временную сложность до более низкого класса сложности без фундаментального изменения способа работы алгоритма?

Редактировать: я реализовал вариант иерархии граничных объемов (BVH), предложенный @meowgoesthedog. Пересечение прямоугольника и треугольника было немного сложно реализовать, но в остальном лежащую в его основе теорию довольно легко понять.

Я экспериментировал с разным количеством разделов и подразделов, и результаты сильно различались, но в большинстве случаев приведение лучей работает значительно лучше. Универсальной оптимальной конфигурации не существует, поэтому есть смысл попробовать разные числа для разных объектов/сцен. В моем случае 4/2 (деление комнаты на 4*4*4 граничных блока, содержащих 2*2*2 подблока в каждом, т.е. 64 блока с 8 внутренними блоками в каждом), 5/2 и 6/2 обычно работают хорошо, хотя для некоторых объектов лучше всего работает неиерархическое разбиение (например, 10/0).

Количество требуемых тестов пересечения луча и треугольника может быть уменьшено до 97% (может быть, больше), но более высокие уровни разделения делают создание граничных боксов / AABB довольно дорогостоящим. При хорошей конфигурации программа работает до 4 раз быстрее, чем решение без граничных томов. Лучшая производительность лучше видна на сценах с большим количеством треугольников (более 10000).

Тем не менее, моя реализация все еще относительно наивна, и я уверен, что есть еще много возможностей для улучшения. Я продолжу возиться и обновлю этот пост, если получу хорошие результаты!


person R.G.    schedule 26.08.2017    source источник
comment
Временная сложность O(n), а не O(n^2). Но O(n) для каждого пикселя все же намного хуже, чем O(log n)   -  person meowgoesthedog    schedule 26.08.2017
comment
Можете ли вы объяснить, почему это O (n), а не O (n²)? Для 1000 треугольников, согласно моему текущему алгоритму, нужно выполнить вычисление точки пересечения один миллион раз (т.е. 1000 раз для каждого треугольника или луча).   -  person R.G.    schedule 26.08.2017
comment
Не знаете, почему вы думаете, что вам нужно 1000 тестов для каждого треугольника? Вам нужно протестировать все n треугольников для каждого луча, но количество лучей зависит от вашего разрешения рендеринга и количества образцов, а не (обязательно) вашего количества треугольников.   -  person meowgoesthedog    schedule 26.08.2017
comment
@meowgoesthedog Я отбрасываю n лучей, каждый из которых направлен в центральную точку одного из n треугольников (я называю это целевым треугольником). Для каждого из n лучей мне нужно проверить, есть ли другие треугольники, которые пересекают луч на меньшем расстоянии от начала луча, чем целевой треугольник. Чтобы сделать это, мне нужно выполнить тест пересечения для всех других треугольников для каждого луча.   -  person R.G.    schedule 26.08.2017
comment
Возможно, у вас неправильное понимание трассировки лучей (или трассировки пути, когда используется источник света) — лучи испускаются из камеры, а не из источника. Хотя это то, что физически происходит в реальном мире, когда дело доходит до вычислений, съемка лучей от источников света расточительна, так как нет абсолютно никакой гарантии, что лучи вообще попадут в камеру. Кроме того, если вы проведете луч в середину, вы не примете во внимание размер треугольника. Я рекомендую читать по этому вопросу, ресурсов для которых много.   -  person meowgoesthedog    schedule 26.08.2017
comment
@meowgoesthedog Я не использую трассировку лучей для рендеринга сцены, поэтому камера не имеет большого значения. Я использую его, чтобы вычислить, какие части триангулированного объекта напрямую попадают под лучи (т. е. я использую приведение лучей вместо трассировки лучей, потому что отражение, свойства материала и т. д. не имеют значения), а какие части — нет. Что касается размера треугольника: я делю треугольники поровну, пока они не будут иметь нужный мне размер.   -  person R.G.    schedule 26.08.2017
comment
А, я вижу, это объясняет O(n²). В этом случае вы вычисляете карту окружающего затенения. Однако простое попадание луча в середину каждого треугольника в общем случае не сработает (рассмотрите очень большой треугольник, но если треугольники очень маленькие, это может работать как грубое приближение). Точное аналитическое решение будет очень сложно вычислить — вам нужно будет сначала найти контурный многоугольник любого промежуточного меша (с точки зрения источника света), затем спроецируйте этот полигон на сетку, которую вы хотите запросить.   -  person meowgoesthedog    schedule 26.08.2017


Ответы (1)


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

Сделать это можно с помощью структуры данных spatial-hierarchy; это уменьшит пространство поиска в геометрической прогрессии, что даст вам логарифмическую временную сложность. Три из наиболее часто используемых таких структур: 1. Octrees, 2. Иерархии граничных объемов (BVH) и 3. KD-деревья. >.

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

Для KD-деревьев это хорошая отправная точка; этот документ хорошо известен в сообществе специалистов по трассировке лучей и очень полезен для дальнейших исследований.

(Еще одна структура — знаменитый Binary-Space Partition (BSP). Он дает лучшую производительность, чем все три вышеперечисленных. Однако построение оптимального BSP-дерева слишком затратно для однократного рендеринга с трассировкой лучей.)

Просто чтобы дать вам представление о потенциальных выгодах даже от простой реализации, я использовал KD-дерево в своем собственном проекте трассировки лучей. При разрешении 1920x1080 с треугольной моделью 100K, простым ламбертовским шейдингом и 100 выборками на пиксель рендеринг занял всего 7 секунд. Я попробовал наивный алгоритм O(n) только с 1 выборкой на пиксель и разрешением 320x240, и это заняло 10 минут.

person meowgoesthedog    schedule 26.08.2017
comment
Спасибо, звучит многообещающе. Я посмотрю документы и попытаюсь реализовать это в своей программе. - person R.G.; 26.08.2017