Невозможные пространства в Unity. Порталы.
Дисклеймер: Автор подразумевает что читатель так или иначе знаком с движком Unity и умеет использовать базовый функционал. В этой статье я опишу логику работы порталов из Portal и не только в юнити, различия разных реализаций и частично затрону некоторый код.
Различия паттернов применения
Наиболее интересный механически паттерн - непосредственно аналог порталов из Portal (Далее "Обычные" порталы) со всеми вытекающими, в частности размещение порталов используя порталган на определенных типах поверхностей.
Левел дизайн паттерн - оптимизированные статические порталы (они буквально расставлены заранее), предназначенные для связи нескольких локаций и создания невозможных пространств (далее оптимизированные порталы).
Что нужно чтобы сделать порталы в Unity?
Зависимости - URP, UniRx, DoTween, UniTask
В целом можно обойтись и без вышеупомянутых пакетов, большая их часть призвана ускорить процесс разработки и минимизировать количество ошибок.
Реализация о которой сегодня пойдет речь включает вышеупомянутый набор инструментов.
С чего начинаются порталы?
С начала брат. Не буду вдаваться в тонкости моделирования, просто обозначим что портал для движка всего навсего набор вершин, ака меш.
Да, окна в другие пространства сделаны из меша, а вы как хотели?
Превращаем доску в окно.
Шейдеры или же шайтан-программы которые выполняются на вашем графическом чипе и могут нагреть не только радиатор вашей видеокарты, но и пару мягких булок того кто их пишет (проверено, ручаюсь) помогут нам в этом.
В случае с порталами задача шейдера - превратить плоское нечто в нечто с эффектом глубины, а если быть точнее, то спроецировать пространство за противоположным порталом на портал на который мы смотрим.
Это происходит за счет проецирования UV меша в пространство экрана. Понятно? Скорее нет, чем да. Вот как это выглядит на примере:
Можете представить будто вы наложили текстуру на всю площадь экрана и стерли ее везде кроме того места где есть меш - вот этим шейдер и занимается (на самом деле нет, но результат тот же).
В репозитории этого проекта будет 2 |стула| шейдера - реализация в шейдерграфе и трушном шейдерлабе. Тут каждый сможет выбрать наиболее приятный ему стул :)
А где объем то?
Самое интересное дамы и господа!
С шейдерами разобрались, теперь нам надо получить изображение с другой стороны портала. На бумаге это будет выглядеть так:
Еще помните что делает шейдер? Вот если запихнуть в него то что рендерит камера - мы получим эффект глубины (при условии что камера в точности повторяет наше положение относительно портала)
А как двигать камеру? Вопрос реализации, лично видел около 5ти разных способов, но все эти заключаются в инверсии позиции камеры игрока относительно портала в который мы смотрим.
Кстати для "обычных" порталов понадобится аж две текстуры и несколько итераций рендеринга в одном кадре (чувствуете, чувствуете? Пахнет горячим GPU), но об этом позже.
Лично я использовал такой вариант для оптимизированного портала:
Собстна переход через портал меняет состояние "Путешественника", в связи с чем мы меняем положение камеры, которая начинает обрабатывать положение уже относительно второго портала (да, тут четко определен первый портал и второй, и пока активен первый - второй будет показывать всякую срань, ибо у них одна текстура)
Стоит иметь ввиду, что вычисления не всегда однозначны, в частности это касается значений с плавающей запятой, так что позиции в пространстве для порталов следует выбирать кратными - особенно сильно это касается поворота.
Как это использовать? Ну, например так:
Еще поясню за клиппинг - если за нашим порталом находится какой-то обьект, то по дефолту камера будет его видеть и транслировать в текстуру, в итоге вместо нормального портала мы увидим какую-нибудь невнятную кашу за пределами карты.
Есть два путя решения этой проблемы:
- Клиппинг переднего плана камеры до нашего портала
- Отключение рендеринга для некоторых слоев
Клиппинг сам по себе реализуется довольно просто, но есть несколько НО. Например если мы просто укажем камере с какого расстояния она должна начинать рендерить изображение, то мы столкнемся с артефактами, если камера имеет угол от портала не равный 90 поскольку плоскость клиппинга параллельна камере:
Важно! Эти артефакты будут заметны при наличии обьектов вблизи выходного портала. Поэтому такой вариант сработает если портал не касается плоскостей своими ребрами. Пример:
Собстна самое простое и эффективное решение для оптимизированных порталов - LayerMask. Создаем слой с каким-нибудь говорящим названием типа Ignore и раскидываем его на мешающие обьекты, после чего отключаем их рендеринг в нашей виртуальной камере - все.
А что если портал в портале?
Для достижения такого эффекта нашему GPU придется попотеть. Для начала нам нужно определить количество итераций - в теории можно рисовать портал в портале неограниченно количество раз, но на практе будет достаточно 2-3 итераций.
И так. Для каждого портала у нас существует своя рендер текстура, и для каждой из них мы должны сформировать рекурсивный вид (если мы видим портал конечно), т.е определенное количество раз кратно отдалить виртуальную камеру, отрендерить по порядку от самой дальней камеры и склеить все это в конечную текстуру для портала.
Я использовал вот такую бандуру:
Мы берем одну камеру и двигаем ее для каждого портала отдельно:
На бумаге это будет выглядеть так:
Далее - перемещение между порталами.
Суть в чем - есть у нас портал А и портал В, пихая что-то в портал А мы ожидаем появление этого чего-то торчащим из портала В. Поскольку мы не можем физически разрезать один меш и раскидать его по нескольким местам (на самом деле можем, но это уже тема на отдельную статью, ибо писанины сильно больше), мы будем клонировать наш обьект-путешественник и синхронизировать его положение в обоих порталах.
Собственно манипуляции такие:
- при попадании обьекта в триггер портала создать(включить) его клон
- отключить взаимодействие с коллайдером стены на которой висит портал (только для обычных порталов)
- пока обьект не покинул триггер зону двигать его клона инверсивно относительно портала
- если клон больше чем на половину выполз за пределы портала - поменять местами клон и оригинал, а также сделать активным другой портал (коллайдер то только на оригинале есть)
Оптимизированные порталы подразумевают условный проход, который был создан заранее, поэтому никаких дополнительных манипуляций с коллайдерами нам не нужно. В отличии от обычных, которые могут быть установлены на любую поверхность у которой обычно есть коллайдер.
Для обычного портала добавится проверка на то что второй портал существует
Собственно метод обновления позиции клона и метод который меняет местами клона с оригинальным объектом.
Делэй для предотвращения зацикливания:
Примерно так можно реализовать порталы в юнити.
Не время для итогов
"Обычные" порталы также подразумевают алгоритм их расстановки на подходящей геометрии.
Кратко - мы пускаем луч из камеры в нужную нам точку, проверяем есть ли там достаточно пространства для установки портала и разворачиваем портал по нормали как-бы накладывая его на задетую поверхность. Непосредственно к порталам все остальное отношения не имеет, организация такого рода систем может разниться от проекта к проекту, поэтому тут я откланяюсь.
Тут (да, это мой девлог в тг, все нормально) вы можете найти ссылку на репозиторий с вышеописанным проектом и самостоятельно поковыряться в нем.