Урок по использованию геометрических объектов, текстур и шейдеров в Processing
Содержание материала
Как ясно из названия, в этом уроке будут рассмотрены самые основы. Цель статьи описать и наглядно продемонстрировать использование основных «строительных блоков» любого 2D или 3D проекта. Поэтому урок даёт общее понимание основ, а не является пошаговой инструкцией. Весь приведённый в статье код содержит комментарии, так что становится ясно, что делается в каждой конкретной строке. Поэтому понимание и переделка скетчей под ваши нужды не должны поставить труда.
Урок начнётся с самых основ – создания геометрических объектов в двух измерениях. Затем будет показано, как добавлять текстуры к этим фигурам. На примере 2D понять концепции текстурирования гораздо проще. Текстурирование используется для разных нужд, а не только как способ размещения статических изображений на геометрических объектах. Один из примеров в этой статье покажет, как можно использовать текстуры и спрайты для создания анимации!
После того, как будут получены основы работы с 2D, можно будет переходить и к 3D. Начнём с летающих разноцветных пирамид, а затем создадим Землю с вертексами, нормалями, текстурами и правильными координатами для текстур. Особенно важно последнее – получение правильных координат для текстур может быть достаточно сложной задачей.
И наконец, рассмотрим применение шейдеров к 3D объектам. С помощью шейдеров можно создавать удивительные вещи. Можно заняться освещением и мультитекстурами, но это только для начала. Шейдеры позволяют работать с очень специфичными параметрами, которые в OpenGL, например, обрабатываются автоматически. Например, Вы сможете менять позиции вертексов в вертексных шейдерах (этот пример приведён в уроке).
Чтобы всё описанное выше корректно работало, различные параметры скетча (сам основной скетч, 3D объекты и шейдеры) должны быть правильно настроены и согласованы друг с другом. Примеры кода из урока покажут, как это правильно сделать. Как только Вы изучите основы, у Вас появится база, пользуясь которой Вы сможете создать более продвинутые программы. Давайте начнём …
Примеры кода
Все примеры кода из этой статьи можно скачать с GitHub - https://github.com/AmnonOwed. Они написаны в Processing 1.5.1. с использованием библиотеки GLGraphics 1.0.0, написанной Андресом Колубри. Как Вам известно в Processing 2.0 появилось много улучшений по сравнению с предыдущими версиями. Одно из наиболее интересных – улучшение OpenGL-рендеринга. Многие из тех вещей, которые раньше приходилось делать с помощью GLGraphics, теперь можно реализовать с помощью собственных методов и классов Processing без использования сторонних библиотек. Примеры кода портированы для Processing 2.0b8/2.0b9. Но так как это пока только бета-версия, то некоторая функциональность может быть плохо документирована. Добавьте к этому сложности в работе с GLSL-шейдерами, и Вы поймёте, что работа с бета-версией может стать настоящей проблемой. Несмотря на то, что все примеры кода работают в новой версии Processing, они не являются точной копией примеров для версии 1.5.1.
К счастью GitHub поддерживает совместную разработку, так что не стесняйтесь – создавайте дополнительные ветви и улучшайте код. Лучше будет, если Вы скачаете весь репозиторий целиком. Тогда все примеры кода точно будут работать, так как будут правильно располагаться в иерархии папок и будут правильно обращаться к папке с общим изображениями. Лучше использовать версию Processing 1.5.1 в сочетании с GLGraphics, но если желаете, можете выбрать другую версию или даже установить несколько версий Processing на Ваш компьютер. Тогда Вы сможете скачать и протестировать разные версии кода. Ниже приведены необходимые ссылки:
- Любую из версий Processing (1.5.1, 2.0b8 и 2.0b9) можно скачать здесь - http://processing.org/download/.
- Примеры кода для Processing 1.5.1.+ GLGraphics 1.0.0 находятся в репозитории - https://github.com/AmnonOwed/P5_CanTut_GeometryTexturesShaders.
- Примеры кода для Processing 2.0b8/2.0b9 доступны здесь - https://github.com/AmnonOwed/P5_CanTut_GeometryTexturesShaders2B8.
Создание геометрических объектов в 2D
Как уже было сказано во введении к уроку, мы начнём с основ, а затем будем расширять границы использования одних и тех же базовых техник. Первый шаг на этом пути – это создание двумерных геометрических фигур. Для этого Processing предоставляет встроенные методы, такие как rect(), ellipse() и triangle(). Каждый из них имеет широкие области применения и удобен для нас. Однако если Вам захочется большей гибкости, лучше использовать вертексы для создания произвольных фигур. Использование вертексов откроет доступ к таким вещам, как повертексное окрашивание, работа с нормалями и uv-координатами текстур для каждого вертекса. Для этих целей в Processing существует несколько полезных методов: beginShape, endShape, vertex и texture.
В методе beginShape можно использовать различные параметры (такие как TRIANGLES, QUADS и т.д.), чтобы указать какую фигуру Вы хотите создать (треугольник, квадрат и т.д.). Документация к методу beginShape содержит примеры кода и изображение фигуры, получаемое с помощью этого кода. С одним и тем же набором вертексов можно сформировать различные фигуры в зависимости от значения параметра.
В примере Custom2DGeometry на экран выводятся все возможные типы фигур. Кроме того пример демонстрирует, что OpenGL без труда создаёт переходы цветов между вертексами в пределах фигуры. Таким образом, можно создавать интересные многоцветные полигоны.
Текстурирование в 2D
Итак, Вы познакомились с базовыми методами Processing для создания геометрических фигур, пора переходить к текстурам. Текстурирование – это размещение изображения на поверхности геометрического объекта. Так же как для создания фигуры надо было задать позиции вертексов, так и для размещения текстуры надо задать её положение на объекте. Это положение называется uv-координатами или координатами текстуры. В Processing значения этих координат можно задать в размерах изображения или в нормализованном виде. Лучше работать с нормализованными значениями, так как этот подход является более гибким (например, если Вам понадобиться изменить размер изображения). OpenGL тоже работает с нормализованными значениями (например, при работе с шейдерами).
В примере FixedMovingTextures2D создаются те же фигуры, что и в предыдущем примере, но теперь они покрыты текстурами. Благодаря этому возникают различные визуальные эффекты. В большинстве случаев координаты текстур задаются статически. Фигура может перемещаться или поворачиваться, но выглядеть она всегда будет одинаково. Есть и другая возможность – использование динамических координат для текстур. В этом примере продемонстрированы оба варианта.
Пример DynamicTextures2D построен на дальнейшем использовании динамических текстур. Он демонстрирует, как можно изменять координаты текстуры в процессе работы программы и выводит на экран целую сетку из квадратов (QUADS) без падения производительности. Но если тоже самое надо было бы проделать с Pimage, мы бы ощутили значительный удар по памяти и падение производительности.
Пример Texture2Danimation использует рассмотренные выше методы применительно к анимации. Каждый кадр анимации хранится в отдельном изображении. Все вместе они образуют подобие сетки. Смена кадров происходит за счёт перемещения по ячейкам сетки. При этом используются координаты текстур. Как видите, даже с использованием такого неказистого набора спрайтов можно создать иллюзию анимации. Использование же этой простой техники в сочетании с красивыми спрайтами позволяет добиться очень привлекательных результатов.
Работа с трёхмерными объектами
Что-то мы заработались в 2D, пора переходить к объёму! А поможет нам в этом пример Custom3DGeometry. Он представляет собой бесконечный поток летающих разноцветных пирамид. По своей сути этот пример является трёхмерным эквивалентом первого примера по работе с 2D графикой. В нём применяются те же самые методы - beginShape, endShape и vertex. Вся разница заключается лишь в дополнительном измерении. Однако в структуре программы есть одно большое различие. Все 2D-примеры были процедурными, а в Custom3DGeometry используется объектно-ориентированное программирование (ООП).
В программе описан класс Pyramid, который содержит весь код для создания, обновления и отображения фигур. ООП в большинстве случаев будет полезно и при создании 2D-скетчей. Когда Вы начнёте создавать целые 3D-миры, использование ООП станет обязательным условием, учитывая сложность 3D-скетчей. В противном случае Вам будет сложно уследить за программой. Преимущество ООП в том, что этот подход позволяет как можно больше упростить код основного скетча, разместив весь остальной код по классам.
В интернете существует множество ресурсов посвящённых созданию 3D-объектов в Processing. Во-первых, многие математические объекты описаны на таких сайтах, как Wikipedia и подобных ей. Можно использовать эти описания для воплощения объектов в коде. Во-вторых, существует множество открытых примеров исходного кода для создания 3D-геометрии. Даже если пример написан на другом языке программирования, код по созданию геометрии, как правило, легко портируется. И наконец, существуют несколько библиотек для Processing – например, Hemesh, Shapes3D и Toxiclibs - которые облегчают создание сложных трёхмерных объектов.
Текстурирование в 3D
Все приведённые выше примеры были простыми, поэтому использование прямых вызовов не сказывалось на частоте кадров. Но когда Вы начнёте работать со сложными объектами, состоящими из большого числа вертексов, частота кадров может упасть. К счастью существуют классы, которые могут эффективно хранить все необходимые данные способом, не сказывающемся на производительности и работающем с GPU (Vertex Buffer Object). В библиотеке GLGraphics для этих целей есть класс GLModel. В бета-версии Processing для этих целей существует PShape. Обе реализации работают схожим образом, не смотря на небольшие различия в синтаксисе при написании кода. В дальнейшем при упоминании GLModel, можете подразумевать PShape, если Вы работаете с версией Processing 2.0. Использование этих методов позволит создавать геометрические объекты с большим числом вертексов и приличной частотой кадров.
Основные шаги по созданию трёхмерных объектов в примерах таковы:
- Создание необходимых для построения объекта данных (вертексы, нормали, координаты текстур) и хранение их во временных массивах.
- Создание объекта GLModel и перевод данных из массивов в GLModel.
- Отображение объекта.
Первые два шага (создание объекта и создание GLModel) делаются один раз. После этого объект можно отображать на экране.
В примере TexturedSphere реализованы описанные выше шаги для создания виртуальной модели Земли. Относительно легко рассчитать положение точек, которые составляют сферу. И достаточно просто разместить на ней несколько полигонов. А вот, что действительно сложно, так это рассчитать координаты текстуры. Работа с координатами текстур всегда сложна, потому что Вам приходится накладывать плоское изображение на трёхмерный объект. Поэтому на объекте всегда будут области, которые будут выглядеть не совсем правильно.
При написании этого кода автор столкнулся с такой проблемой (неправильное размещение текстуры на полюсах и стыках изображений ), и нашёл несколько статей на эту тему:
http://tat-tvam-asi.in/hacking/webgl/sphere-shader/
http://sol.gfxile.net/sphere/index.html
http://gamedev.stackexchange.com/questions/33631/textureing-subdivided-icosahedron-in-xna-seam-problem
http://howevangotburned.wordpress.com/2011/02/28/the-oddyssey-of-texturing-a-geodesic-dome/
Быть может, стоило поискать существующий код, решающий эту проблему? И в самом деле, после недолгих поисков был найден код на C++, написанный Габором Паппом, который и был портирован в Processing. Метод Габора основан на сегментации, которая очень полезна при задании плотности меша с равномерным распределением вертексов по всей форме. Этот код, скорее всего, может быть оптимизирован, потому что он рисует вертексы с одним и тем же положением, но не использует индексы для их повторного использования. Проблема в том, что в некоторых случаях вертексы находятся в одной и той же позиции, но имеют разные текстурные координаты. Как бы там ни было, данный пример даёт реальный результат – корректно текстурированную сферу (разбитую на икосаэдры), хранящуюся в GLModel.
Применение шейдеров к объектам
Теперь, когда Вы создали 3D–объект и разместили его в GLModel, настало время перейти к следующей задаче – применению к объекту шейдеров. Шейдеры завоёвывают всё большую аудиторию, отчасти, благодаря таким инструментам, как Shadertoy [https://www.shadertoy.com/]. К сожалению, таким инструментам не хватает дружелюбного пользовательского интерфейса. Тем не менее, их небывалая мощь и грядущая поддержка шейдеров в 2.0 делает эти инструменты привлекательными для виджеев. В этом уроке будут затронуты два типа GLSL шейдеров: вертексные и фрагментные. В TexturedSphereGLSL за основу взят предыдущий пример кода и добавлено применение GLSL шейдеров. Вы можете взять оба примера и, сравнив их, увидеть добавленный код, отвечающий за использование шейдеров, хотя результат работы обоих примеров может быть очень схож или даже абсолютно идентичен. В этом примере можно двигать мышью, чтобы поменять освещаемую позицию.
В примере MultiTexturedSphereGLSL сделан ещё один шаг – добавлено несколько текстур. В зависимости от освещения рассчитываются шейдеры для получения итогового изображения. Например, можно сделать день, ночь или нечто среднее. Также здесь добавлен слой облаков нависших над поверхностью Земли. Эти примеры должны помочь Вам понять основы применения шейдеров к 3D-объектам.
Дополнительные эффекты при работе с шейдерами
Вертексное смещение (Vertex displacement). Это удивительный эффект. Увидев видео с его использованием, Вам без сомнения захочется воссоздать его в своём коде. Здесь будут представлены два типа смещения – смещение двумерной карты высот и смещение трёхмерной сферы. Кроме того, каждый из примеров имеет по две вариации: с другим входным изображением и с процедурным шумом, генерируемым внутри самого шейдера. Названия примеров говорят сами за себя, так что Вам должно быть понятно, что именно каждый из них делает. Основная идея вертексного смещения заключается в том, что позиция каждого вертекса смещается вдоль нормали на определённую величину.
Эта величина определяется двумя значениями: displaceStrength (глобальное смещение, которое применяется ко всем вертексам) и индивидуальным смещением, которое различно для каждого вертекса. Индивидуальное смещение обычно определяется картой (изображением). Также часто бывает, что индивидуальное смещение определяется шумом Перлина (perlin noise). Пример использования каждого из смещений можно увидеть в видео к этой статье (в самом верху).
GLSL_TextureMix – это пример ещё одного шейдерного эффекта. Здесь показано, как можно слить несколько текстур в одну. В основном скетче можно задать различные режимы смешения, которые будут оказывать влияние на код в шейдере. Конечно, лучше создать несколько различных шейдеров и переключаться между ними. Но этот урок не нацелен на оптимизацию, а приведённый код наилучшим образом демонстрирует основы использования GLSL. Этот эффект смешения текстур делается внутри фрагментного шейдера.
Преимуществами смешения текстур внутри фрагментного шейдера являются автоматическая интерполяция и нормализация координат текстуры. Это не просто слияние изображений. Вам не нужно заботиться о количестве пикселей, а нужно только хранить координаты текстур в диапазоне от 0 до 1 – в результате получите идеально гладкое смешение текстур.
Проблемы
Почти всегда при написании своего кода, что-то начинает идти не так. Вот несколько советов, если Вы столкнулись с «необъяснимыми» проблемами.
- При использовании некоторых видеокарт ATI Radeon компилятор начинает выдавать сообщения об ошибках (впрочем, совсем безобидные) такого вида: “Validation warning! – Sampler value [имя_первой_текстуры] has not been set.” Это сообщение можно просто проигнорировать. Оно никак не отразится на работе скетча.
- В бета-версии Processing в консоль могут выводиться предупреждения такого вида: “Display 0 does not exist, using the default display instead”. Это сообщение также можно просто проигнорировать. Оно никак не отразится на работе скетча
- Существует ещё несколько типов сообщений об ошибках, наподобие таких: “Error: OpenGL error [номер ошибки] at top endDraw(): [текст сообщения об ошибке]”.
Общая рекомендация – ошибки по 1280 номер можно игнорировать без каких-либо последствий для Вашего проекта. В то время как ошибки 1281, 1282 и т.д. оказывают существенное влияние на компиляцию скетча, он может просто не запуститься. Если скетч запустился без проблем, ошибку можно игнорировать. Если же экран стал чёрным, с ошибкой нужно разобраться. Такие ошибки появляются при использовании старых видеокарт, устаревших драйверов или при ошибках в коде. Многие из них, наверняка, уже обсуждались на форуме, посвященном работе в Processing, так что лучшим решением будет поискать, не было ли у кого-то такой же проблемы, и посмотреть как она была устранена.
Замечания
В одном уроке невозможно охватить всё, но этот урок – неплохое введение в основы работы с геометрическими объектами, текстурами и GLSL шейдерами. Возможно, что самым главным для Вас была возможность скачать примеры кода и посмотреть, как он работает. Попытайтесь понять, как основной скетч работает с данными внутри шейдеров. Скорее всего, на глубокое понимание этого уйдёт какое-то время. Тем более, что многие аспекты работы с GLSL не являются интуитивно понятными. Что можно сказать? Кодируйте, учитесь, старайтесь делать нестандартные вещи! За поддержкой обращайтесь на форум - https://forum.processing.org/. Если у Вас возникают непонятные ошибки, и Вы уверены, что проблема не в Вашем коде, высылайте отчёт на https://github.com/processing/processing/issues. Убедитесь, что сопроводили свой отчёт чётким описанием и примером кода, вызывающего ошибку, в противном случае она не будет исправлена.