Сервис для
сео - оптимизаторов

Найди ошибки на сайте
Ошибки мешают продвижению сайта
Исправь ошибки на сайте
Сайт без ошибок продвигать легче
Получи новых клиентов
Новые клиенты принесут больше прибыль

2D-освещение в реальном времени в GameMaker Studio 2 - Часть 2

Технология

В последний пост Я показал вам, как создать основы 2D-системы освещения, поэтому в этом примере я хочу продолжить текущий пример и сделать его больше похожим на реальный точечный источник света. Мы также представим небольшое использование шейдеров, чтобы немного упростить процесс - да, шейдеры могут быть простыми! - а затем сделай свет цветным. Давайте напомним себе, куда мы идем в прошлый раз ...

Давайте напомним себе, куда мы идем в прошлый раз

В конце последнего поста я сказал, что нашей следующей задачей будет сделать все, что находится за пределами светового радиуса черным, так как это даст нам приятное ощущение "точечного света", поэтому давайте сделаем это сначала. Прежде чем мы начнем работать над этой механикой, я собираюсь переместить рендеринг этих теневых объемов на поверхность, так как это может быть легко обработано шейдером. Имейте в виду, что многие из этих изменений будут разбросаны по разным сценариям, поэтому нам придется немного попрыгать - так что продолжайте!

В событии create вашего светового объекта добавьте эту переменную в:

прибой = -1;

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

if (! surface_exists (surf)) {surf = surface_create (room_width, room_height); }

Теперь непосредственно перед циклом плитки и перед vertex_begin () мы установим эту поверхность в качестве текущей, так что рендеринг теневого объема перейдет к ней вместо экрана. Мы изменим наш цикл так, чтобы он теперь выглядел примерно так ...

surface_set_target (серфинг); draw_clear_alpha (0,0); vertex_begin (VBuffer, VertexFormat); for (var yy = starty; yy <= endy; yy ++) {for (var xx = startx; xx <= endx; xx ++) {// Создание теневого тома. }} vertex_end (VBuffer); vertex_submit (VBuffer, pr_trianglelist, -1); surface_reset_target (); draw_surface (серфинг, 0,0);

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

Круто, теперь это становится действительно интересным! Теперь мы создадим наш шейдер и получим его для обработки этой новой поверхности. Опять же, он не должен выглядеть иначе, чем сейчас - пока. Щелкните правой кнопкой мыши на элементе ресурсов шейдеров и выберите «Создать». Это должно добавить шейдер и открыть его готовым к редактированию.

Это должно добавить шейдер и открыть его готовым к редактированию

Это даст нам простой шейдер по умолчанию, и все, что нам нужно сделать, это установить этот шейдер перед рисованием поверхности, а затем сбросить его после. Это обходит вызов draw_surface () (и предполагает, что вы только что вызвали свой шейдер shader0 ).

shader_set (shader0); draw_surface (серфинг, 0,0); shader_reset ();

Опять же, это будет выглядеть точно так же, как и раньше, только теперь мы можем добавить некоторые интересные моменты к нему! Сначала нам нужно передать определенное пользователем значение, которое мы можем передать из нашего события отрисовки источников света. Внутри кода шейдера Fragment добавьте эту строку чуть выше void main () ...

униформа vec4 u_fLightPositionRadius; // x = lightx, y = светлый, z = радиус света, w = не используется

Далее нам нужно передать координату комнаты в шейдер Fragment из шейдера Vertex. Это довольно просто, так как координата, которую мы генерируем для теневых объемов, уже находится в координатах комнаты, поэтому нам просто нужно пройти через это. Чтобы сделать это, мы добавляем другое переменное значение в шейдер Vertex, прямо под другими - как это:

варьирующийся vec2 v_vTexcoord; варьирующийся vec4 v_vColour; различные vec2 v_vScreenPos;

Затем в нижней части скрипта Vertex, прямо перед }, мы копируем местоположение.

v_vScreenPos = vec2 (in_Position.x, in_Position.y);

Вам также нужно поставить «варьирующийся vec2 v_vScreenPos»; линия в шейдер фрагмента. Теперь, когда шейдер Fragment имеет все, что ему нужно, мы можем перейти в светлое положение внутри события light draw. Для этого нам нужно получить дескриптор переменной u_fLightPositionRadius внутри события create объекта light. Мы делаем это с самого начала, так как это не очень быстро, поэтому если мы сделаем это один раз с самого начала, это будет хорошо и быстро позже. В нижней части события create источника света добавьте эту строку в:

LightPosRadius = shader_get_uniform (shader0, "u_fLightPositionRadius");

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

shader_set (shader0); shader_set_uniform_f (LightPosRadius, lx, ly, rad, 0.0); draw_surface (серфинг, 0,0); shader_reset ();

Теперь, если мы запустим это - да, это выглядит точно так же - но должно работать нормально!
Однако теперь мы наконец можем использовать эту дополнительную информацию в фрагментном шейдере. Взяв положение комнаты ( v_vScreenPos ) и определив расстояние до положения источника света ( u_fLightPositionRadius.xy ), а затем проверив, не превышает ли он радиус, мы можем определить, находится ли он в пределах диапазона освещения, а если нет, то вывести черный пиксель. Вы также должны увеличить радиус света (переменная rad в событии отрисовки до 256). Давайте посмотрим, как мы используем это в шейдере Fragment, вот весь шейдер ...

варьирующийся vec2 v_vTexcoord; варьирующийся vec4 v_vColour; различные vec2 v_vScreenPos; униформа vec4 u_fLightPositionRadius; // x = lightx, y = светлый, z = радиус света, w = неиспользуемый void main () {// Обрабатываем вектор из местоположения комнаты до источника света vec2 vect = vec2 (v_vScreenPos.x-u_fLightPositionRadius.x, v_vScreenPos.y -u_fLightPositionRadius.y); // вычисляем длину этого вектора float dist = sqrt (vect.x * vect.x + vect.y * vect.y); // если в диапазоне использовать теневую текстуру, если нет, то она черная. if (dist <u_fLightPositionRadius.z) {gl_FragColor = v_vColour * texture2D (gm_BaseTexture, v_vTexcoord); } else {gl_FragColor = vec4 (0.0, 0.0, 0.0, 1.0); }}

Как видите, это довольно простой расчет, но он даст нам довольно крутой эффект точечного источника света и всего, что за черным.

Как видите, это довольно простой расчет, но он даст нам довольно крутой эффект точечного источника света и всего, что за черным

Так как это круто! Перемещая мышку, мы получаем крутой свет, отбрасывающий тени на мир. Мы можем улучшить даже этот простой эффект, немного уменьшив результирующее значение ALPHA, чтобы мы могли видеть сквозь него. Прямо перед финалом } в шейдере Fragment добавьте эту строку ....

gl_FragColor.a * = 0,5;

Для этого нужно взять конечную альфу (которая в нашем случае равна 1,0 для тени или 0,0 для не тени) и уменьшить ее, чтобы сделать ее более прозрачной. Это приводит к изображению ниже ...

Это приводит к изображению ниже

Хорошо, так позвольте мне сделать немного в сторону здесь. Выше вы заметите, что когда я работаю с переменной vect, я делаю vec2 (xx, yy) . Я просто делаю это здесь, чтобы прояснить ситуацию, но на самом деле мне не нужно этого делать. Шейдеры работают в векторах , и это означает, что мы можем делать несколько вещей одновременно. Эта строка должна выглядеть так ...

vec2 vect = v_vScreenPos.xy-u_fLightPositionRadius.xy;

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

Следующее, что я хотел бы добавить, - это немного цвета, но перед тем, как сделать это, я хочу остановить этот жесткий край света и добавить то, что мы называем, свет падает. Если вы не очень близки к источнику света, у вас никогда не будет такого острого края, который у нас есть, на самом деле он будет медленно угасать, поэтому было бы неплохо добавить это к нашему свету. Мы сделаем линейное падение, но вы можете использовать кривую или что-то еще, если вы чувствуете себя умным. Вычисление спада действительно простое, на самом деле у нас уже есть нужные нам значения. Расстояние от света и радиус света - это все, что нам нужно. Так как у нас уже есть немного кода, который работает, когда внутри радиуса, мы просто должны разработать шкалу от 0,0 до 1,0 (от нахождения внутри радиуса до ее края), а затем LERP (линейная интерполяция) теневого объема для завершения тень. Вот замена внутреннего раздела шейдерного фрагмента:

// если в диапазоне использовать теневую текстуру, если нет, то она черная. if (dist <u_fLightPositionRadius.z) {// обработать значение от 0 до 1 от центра до края радиуса float falloff = dist / u_fLightPositionRadius.z; // получить теневую текстуру gl_FragColor = v_vColour * texture2D (gm_BaseTexture, v_vTexcoord); // теперь LERP от фигуры теневого объема до общей тени gl_FragColor = mix (gl_FragColor, vec4 (0.0,0.0,0.0,0.7), falloff); } else {// вне радиуса - полностью в тени gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.7); }

LERP внутри шейдера называется mix (по какой-то странной причине), но это одно и то же. в зависимости от значения спада он будет медленно смешиваться (я полагаю) с gl_FragColor до 0,0,0,0,7 (общая тень). После того, как вы удалите gl_FragColor.a * = 0,5; с конца сценария фрагмента шейдера, это даст нам прекрасный спад и настоящее ощущение темноты и света.

Следующая логическая вещь, которую нужно сделать, это добавить немного цвета. Поэтому сначала дважды щелкните по экземпляру в комнате и установите его зеленый цвет - вот так:

Поэтому сначала дважды щелкните по экземпляру в комнате и установите его зеленый цвет - вот так:

После того, как вы это сделали, измените линию в событии отрисовки источников света, где мы рисуем поверхность, чтобы мы могли передать этот цвет следующим образом:

draw_surface_ext (серфинг, 0,0,1,1,0, image_blend, 0,5);

Это отправит зеленый цвет (и 0,5 альфа) шейдеру, чтобы мы могли использовать его для установки светлого цвета. Далее мы собираемся изменить внутреннее ядро ​​шейдера Fragment снова, чтобы иметь дело с цветом. Итак, где у нас была строка gl_FragColor = v_vColour * texture2D () , теперь мы изменим на это ...

vec4 col = texture2D (gm_BaseTexture, v_vTexcoord); if (col.a <= 0,01) {gl_FragColor = v_vColour; } else {gl_FragColor = col; }

Мы проверяем значение альфа ( col.a ), так как оно равно 1,0 для полной тени и 0,0 для не в тени, поэтому на это простое удобное число. Мы также не сравниваем с точным 0.0, так как с плавающей запятой вы никогда не можете быть точно уверены, какое будет значение, поэтому мы всегда должны вносить небольшое поле для ошибки. Теперь, если мы запустим это, вы должны получить прекрасный зеленый свет!

Теперь, если мы запустим это, вы должны получить прекрасный зеленый свет

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

Итак ... теперь вы уже на пути к созданию собственного двигателя освещения! Ниже вы можете увидеть мои собственные усилия, когда я вышел за пределы этого единого источника света в несколько динамических источников света. Нажав на изображение ниже, вы попадете на ряд видеороликов, демонстрирующих мой прогресс с использованием этой же системы, и хотя некоторые из моих действий более продвинуты (например, нанесение нескольких теней на одной поверхности), вам, конечно, не нужно попробуйте скопировать их, чтобы ваша собственная система освещения работала, она будет работать без каких-либо дополнительных дополнений.

Похожие

Волкер о времени без выстрелов в АТО: при наличии политической воли мир возможен
... витель Госдепа США по Украине прокомментировал отсутствие нарушений перемирия на Донбассе / УНИАН Специальный представитель Государственного департамента Соединенных Штатов Америки по делам Украины Курт Волкер заявляет, что мир на Донбассе возможен лишь при наличии политической воли всех сторон придерживаться ранее согласованного перемирия. Как сообщил американский дипломат в микроблоге Twitter, об этом он сказал сегодня в интервью "Радио Свобода". "Вчера
Купите маршрутизатор TP-Link и получите бесплатное ПО - банк питания и динамик Bluetooth
TP-Link предлагает рекламный набор гаджетов: при покупке одной из моделей маршрутизаторов Archer (C5400, C2150, C2300)
Стратегия WebView для разработки под iOS и Android
ЧАСТЬ 1 ВКЛЮЧАЕТ: Соревнование Разработка для Android и iPhone (iOS) - разные звери. Например, если вы создаете приложение для Android, вы должны переписать его как порт для iPhone. Вся эта тяжелая работа х2. Это не идеально, но мы делаем то, что должны, чтобы обойти ограничения,
Как восстановить ваш iPhone или iPad из резервной копии
... частью, восстановление резервной копии на ваш iPhone является простым процессом. При резервном копировании устройства у вас есть два варианта: выполнить резервное копирование в iCloud или в iTunes. Методы восстановления этих двух резервных копий немного отличаются. Вот преимущества и недостатки обоих способов восстановления вашего iPhone. Вы должны восстановить из iTunes или iCloud?
Вы должны восстановить из iTunes или iCloud?