В этом репозитории находится реализация рендеринга порталов с помощью OpenGL. Главной целью не была оптимизация, а было получение максимально корректно работающей программы при
- Большом количестве порталов самых разных форм
- Рендеринге поверхностных порталов
- Рендеринге портала в портале
Но всё-же для этих целей лучше использовать рейтрейсинг на видеокарте, ибо он будет в миллион раз быстрее для сложных сцен и для большой глубины.
Работающую программу можно посмотреть в секции release. Запуск:
.\glut.exe <scene-name>.json
, например: .\glut.exe simple_portals.json
. Программа запустит ту сцену, которая задана в json файле. Так же, вместо того, чтобы открывать консоль, можно пользоваться возможностями drag&drop'а, и просто навести нужную сцену на программу в проводнике.
Требования к версии OpenGL: не менее 3.0.
Управление: кликните правой кнопкой мыши, и там будут показаны все команды и кнопки для быстрого их вызова.
Внимание! Все сцены, представленные здесь уже предрасчитаны, в них нельзя поменять никаких параметров и пересчитать. Движок по симуляции порталов не встроен в данную программу! Текущая программа может только визуализировать.
Все сцены находятся в папке scenes
.
Файл: simple_portals.json
.
Файл: surface_portal.json
.
Поверхностный портал - это портал, полученный соединением нескольких обычных порталов таким образом, что сохраняется непрерывность и целостность пространства с обоих сторон. Просто посмотрите на скриншоты, чтобы это понять. На основе этого базового концепта можно построить очень интересные порталы.
Я решил называть этот портал поверхностным, а не объёмным, потому что это плоская поверхность, а не что-то, имеющее объём. Настоящим объёмным порталом можно назвать червоточину.
Файл: portal_in_portal.json
.
Файл: portal_in_portal2.json
- немного в другом формате.
Вы задавались вопросом что будет если поместить один портал в другой? Я решил этот вопрос, написав собственный движок для симуляции этого процесса (который пока не опубликован). Затем преобразовал полученные данные в формат этого репозитория и получил текущий файл. Так же этот файл отрендерил при помощи portals-tracing и опубликал такое видео на ютубе:
Иногда вы можете наблюдать глитчи при рендеринге последнего кадра. Это связано с тем, что не хватает глубины. Можете поставить глубину побольше через ПКМ, тогда глитчей будет поменьше, но дальше в портале они всё-равно будут видны. Единственное разумное решение у этой проблемы - рендерить рейтрейсингом.
Файл object_fly_into_portal_in_portal.json
.
Здесь квадратный портал немного вошел в свою вторую половинку, и именно в эту часть залетает объект, показывается как он будет двигаться. Видно, что он движется по прямой, но в какой-то момент, можно увидеть одновременно 4 отражения объекта.
Так же на этой анимации тестируется движок на предмет правильности обрезания и телепортации объектов.
Файл my_speed_model.json
.
В интеренете идёт бурное обсуждение о том, как будут вести себя объекты при движении порталов, потому что там возникает много парадоксов и сложностей.
На этой анимации показано что произойдёт, если на объект летит портал. С одной стороны объект остаётся неподвижным, а с другой - он постоянно двигается. Именно с такой моделью скорости работает вычисление потрала в портале. Была ещё такая модель:
Но быстро стало понятно, что она тупиковая, и что с помощью неё нельзя вычислить портал в портале.
Файл portal_no_problem.json
.
В этой анимации показан случай помещения "портала в портал", когда у нас не возникает никаких вопросов, где нет никаких парадоксов. Ведь порталов 4, и мы просто суём независимые.
Кстати, суть вычисления портала в портале, который выше, как раз в том, чтобы разбить исходный портал на такие независимые порталы, где не возникает парадоксов телепортации.
Так же на этой анимации тестируется движок на предмет передачи скорости входящему и выходящему объекту.
Файл: scale_portal.json
.
Файл: slanted_portal.json
.
Файл portal_propeller.json
.
Этот тест призван показать точность данной программы, и её способность обрабатывать нестандартные случаи рендеринга порталов. Другие подходы вряд ли могут похвастаться корректным рендерингом подобной сцены.
Есть множество разных подходов к рендерингу портала, первый - это подход на основе stencil буфера, в нём вся сцена внутри портала рисуется с включенным трафаретом портала, то есть в итоге отобразится только нужная часть сцены. Проблема этого подхода в том, что он неправильно работает с буфером глубины, то есть нарисовать тот же пропеллер из порталов не получится. Решают иногда эту проблему принудительным рисованием в буфер глубины нужного нам полигона.
Пример программ, использующих stencil буфер:
- Mini-Portal_Recursive.
- Modern tutorials.
- glPortal.
- Stackoverflow - how to implement portal rendering - моя программа справится с таким случаем
- Rendering recursive portals with OpenGL.
Другой подход - это рисовать всю сцену в framebuffer, а затем только его часть отображать на экране, при этом задавая глубину такой, как будто рисуется полигон, здесь с буфером глубины всё ок, и пропеллер нарисуется. Именно такой подход и используется в данной программе.
Помимо этого есть ещё несколько трюков, о которых далее будет рассказано поподробнее.
Для начала у нас должны быть две функции:
- Нарисовать всю сцену: сначала рисуются порталы, затем простые полигоны.
- Нарисовать конкретный портал вместе с его содержимым.
Эти функции в процессе рекурсивно вызывают друг друга.
Основной код рендеринга порталов находится в src/prtl_vis/opengl_common.cpp
в функциях SceneDrawer::drawPortal
и SceneDrawer::drawScene
.
Framebuffer - объект, содержащий в себе буфер глубины и буфер цвета.
Можно перенаправить рисование не на экран, а в этот фреймбуфер. Обычно это используются для пост-обработки, или чтобы показать как выглядит буфер глубины, как в этой статье на хабре.
Примеры для framebuffer'ов находятся в файле tests/torus.cpp
.
Главное место, где сосредоточена работа с framebuffer'ами - это файл include/prtl_vis/framebuffer.h
.
Действия над фреймбуферами:
-
Включение/выключение. При включении рисование перенаправляется на текущий фреймбуфер, а при выключении рисование перенаправляется обратно. Причем поддерживается возможность рекурсивно включать фреймбуферы.
-
Нарисовать содержимое фреймбуфера на экран.
-
Объединить два фреймбуфера. Каждый фреймбуфер - это не только цветная картинка, но ещё и карта глубины, поэтому их объединение заключается лишь в выборе пикселей, которые удовлетворяют тесту глубины, то есть самых ближних пикселей из двух фреймбуферов. Это работает как магия, когда мы можем просто объединить две картинки, и они будут выглядеть так, как будто мы всё это рендерились вместе.
-
Нарисовать только ту часть фреймбуфера, которая попадает на полигон - самое главное действие для этой библиотеки. Причем нарисовать так, чтобы в буфере глубины была не глубина из фреймбуфера (тогда такой подход ничем не будет отличаться от stencil), а глубина текущего полигона. Получается, текущий полигон выступает в роли экрана, на котором показывается картинка с миром по ту сторону портала.
Все эти операции реализуются через шейдеры.
Большое спасибо этим источникам:
- Stackoverflow - Render the depth buffer in OpenGL without shaders
- Framebuffers.
- Tutorial 14 : Render To Texture.
В OpenGL есть такое понятие, как передняя и задняя сторона. Вычисляется она при помощи определения как ориентирован спроецированный на экран полигон: по часовой стрелке или нет. В зависимости от этого определяется какую сторону полигона мы видим. К сожалению, алгоритм этого определения не такой тривиальный, как хотелось бы, и если представить более-менее сложный полигон, то вообще становится непонятно что тут ориентация по часовой стрелке, а что против неё. Спасибо stackoverflow за готовый алгоритм.
Для проецирования полигона используется функция include/prtl_vis/opengl_common.h
:projectPolygonToScreen
.
Для определения ориентации полигона используется функция include/prtl_vis/opengl_common.h
:isPolygonOrientedClockwise
.
И для того, чтобы не рисовать лишний раз портал, когда мы видим его заднюю сторону, используется эта функция. Это не только увеличивает производительность, но и избавляет от лишней головной боли типо когда мы хотим скрыть заднюю сторону портала, пытаясь костыльно подставить туда очень близко полигон - вместо этого можно в принципе вместо задней части рендерить полигон.
Мы смотрим на синий портал, видим из него мир, и в этом мире рекурсивно рисуем все остальные порталы, в частности оранжевый портал. А оранжевый портал находится для нас в том же месте, что и синий, после преобразования координат. И получается, что вместо того, чтобы увидеть всю сцену через синий портал, мы увидим в первую очередь заднюю часть оранжевого портала. Именно для того, чтобы это не случалось, нужно поставить такую проверку: не рисовать свою вторую половинку.
В OpenGL с помощью glBegin(GL_POLYGON)
можно нарисовать только выпуклый полигон. Поэтому никаких сложных полигонов в виде буквы С.
Именно для того чтобы нарисовать их, используется тесселяция. Большое спасибо этому сайту, на основе которого сделана тесселяция в этом проекте.
Для работы с тесселированными полигонами имеется файл include/prtl_vis/fragment.h
. Там можно не только разбить полигон на примитивы, но ещё и учитывать координаты текстуры. Так же там же можно нарисовать эти штуки.
Представим, что мы смотрим на синий портал, а за оранжевым у нас находится полигон. Теперь представим, что мы нарисовали содержимое синего портала, но почему-то мы видим этот полигон, хотя он содержится за порталом. Это проблема того, что мы видим те объекты, которые не должны видеть. Тут нам на помощь приходит clip plane - плоскость, обрезающая всю геометрию на два полупространства. Ставим такую плоскость, чтобы она оставляла только то, что находится по ту сторону плоскости портала, и мы больше не видим лишних полигонов.
Но мы ограничены в этих плоскостях, поэтому будем использовать одновременно только одну.
Специально для этого есть файл include/prtl_vis/plane.h
, там есть возможность рекурсивно включать и выключать обрезающие плоскости. Сделано аналогично framebuffer'ам.
У нас есть исходная сцена. Как нам получить сцену, которую мы увидим сквозь портал? Применить к ней преобразования координат! Порталы по сути есть простые преобразования координат, которые смещают и поворачивают сцену. А если вдаваться в подробности, то сначала вся сцена записывается в относительных координатах первого портала, а затем переводится из относительных координат второго портала в абсолютные.
Поэтому каждый раз при рисовании того или иного портала к модельно-видовой матрице применяются преобразования координат. А после работы с текущим порталом, применяется обратная матрица преобразования координат, чтобы вернуть всё к прежнему виду.
Для преобразования координат используется библиотека glm
, а для работы с осями координат и получения матриц библиотека spob
.
Самая дорогая операция - нарисовать портал. Потому что при рисовании потрала в нём есть не только вся сцена, но ещё и другие порталы, которые рекурсивно рисуются. Поэтому мы должны любыми способами отсекать те порталы, которые заведомо не будут видны на экране. По-любому выиграем в скорости работы.
Именно так и делается в программе. При рисовании каждого портала определяется текущая видимая область. При помощи библиотеки clipper
находится объединение этой области с спроецированным полигоном портала, благодаря чему можно определить, когда эта область нулевая и просто не рисовать не видимый портал.
Это дает значительный прирос производительности, несмотря на то, что операция нахождения пересечения полигонов - очень дорогая.
Конечно, можно ещё сделать такую оптимизацию, чтобы из полигона видимости ещё вычитался полигон, который перекрывает портал на экране. Но в этой программе такое не реализовано.
Рисуешь фреймбуфер на полигон при помощи шейдеров, но в шейдерах ClipPlane по умолчанию не учитывается и ты про это не знал? БУМ, У ТЕБЯ БАГ. Решение.
При возвращении обрезающей плоскости забыл что ты изменил координаты, и на самом деле ты не возвращаешь ту плоскость, а добавляешь другую? БУМ, У ТЕБЯ БАГ. Решение.
Сделал портал, у которого левосторонняя система координат, после которой ориентация по часовой стрелке меняется на против-часовой, и внутри обратная сторона портала меняется местами с передней? БУМ, У ТЕБЯ БАГ. Решение.
Вывод: порталы, одна из самых коварных вещей, что я встречал в своей жизни, и они создают баги на пустом месте. Благо в этом репозитории вроде всё работает хорошо... И то я уже ничему не верю, и ни на что не надеюсь. Если захотите рендерить порталы такой же сложности как у меня, то используйте мой код, либо просто переходите на рейтрейсинг. Вот какой черт вас укусил делать порталы на OpenGL? Рейтрейсить порталы - сплошное удовольствие, простое как два пальца. Пользуйтесь мощностью своих видеокарт.
OpenGL Ray Tracer With Portals Demo.
Зависимости: glm, glew, glut, spob, clipper.
Используете CMake. Смотрите в файл CMakeLists.txt
, там находятся инструкции и ссылки на зависимости, которые надо скачать.
Требования к OpenGL: 3.3, так как используются framebuffer'ы.
Если у вас Linux, Intel HD Graphics, OpenGL 3.0, и программа вылетает с ошибкой, возможно вам помогут следующие команды в терминале:
export MESA_GL_VERSION_OVERRIDE=3.3
export MESA_GLSL_VERSION_OVERRIDE=330
Спасибо StackOverflow: How can I get Opengl 3.3..., мне помогло.
- Makefile
- Написать этот файл с объяснениями по-человечески, желательно с картинками.
- Наконец опубликовать секретный проект и рассказать зачем же это всё было нужно
- Framebuffer multisample
- Translate to English