Молнии

в 6:15, , рубрики: game development, Gamedev, Алгоритмы, генерация, Программирование

Вы летите на своём корабле по пещере, уклоняясь от вражеского огня. Однако, довольно скоро вы осознаёте что врагов слишком много и похоже что это конец. В отчаянной попытке выжить вы жмёте на Кнопку. Да, на ту самую кнопку. На ту, что вы приготовили для особого случая. Ваш корабль заряжается и выпускает по врагам смертоносные молнии, одну за другой, уничтожая весь флот противника.

По крайней мере, таков план.

Но как же именно вам, как разработчику игры, отрендерить такой эффект?

Генерируем молнию

Как оказалось, генерация молнии между двумя точками может быть на удивление простой задачей. Она может быть сгенерирована как L-System (с небольшим рандомом во время генерации). Ниже пример простого псевдо-кода (этот код, как и вообще всё в этой статье, относится к 2d молниям. Обычно это всё что вам нужно. В 3d просто генерируйте молнию так, чтобы её смещения относились к плоскости камеры. Или же можете сгенерировать полноценную молнию во всех трёх измерениях — выбор за вами)

segmentList.Add(new Segment(startPoint, endPoint));
offsetAmount = maximumOffset;     // максимальное смещение вершины молнии
for each iteration // (некоторое число итераций)
  for each segment in segmentList // Проходим по списку сегментов, которые были в начале текущей итерации
    segmentList.Remove(segment); // Этот сегмент уже не обязателен

    midPoint = Average(startpoint, endPoint);

    // Сдвигаем midPoint на случайную величину в направлении перепендикуляра
    midPoint += Perpendicular(Normalize(endPoint-startPoint))*RandomFloat(-offsetAmount,offsetAmount);

    // Делаем два новых сегмента, из начальной точки к конечной
    // и через новую (случайную) центральную
    segmentList.Add(new Segment(startPoint, midPoint));
    segmentList.Add(new Segment(midPoint, endPoint));
  end for
  offsetAmount /= 2; // Каждый раз уменьшаем в два раза смещение центральной точки по сравнению с предыдущей итерацией
end for

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

image
image
image
image
image

Не плохо. Уже выглядит хотя бы похоже на молнию. Однако, у молний часто есть ветви, идущие в разных направлениях.

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

direction = midPoint - startPoint;
splitEnd = Rotate(direction, randomSmallAngle)*lengthScale + midPoint; // lengthScale лучше взять < 1. С 0.7 выглядит неплохо.
segmentList.Add(new Segment(midPoint, splitEnd));

Затем, на следующих итерациях эти сегменты тоже делятся. Неплохо будет так же уменьшить яркость ветви. Только основная молния должна иметь полную яркость, так как только она соединенна с целью.

Теперь это выглядит так:

image
image
image

Теперь это больше похоже на молнию! Ну… по крайней мере форма. Но что насчёт всего остального?

Добавляем свет

Первоначально система, разработанная для игры использовала закруглённые лучи. Каждый сегмент молнии рендерился с использованием трёх четырёхугольников, для каждого из которых применялась текстура со светом (чтобы сделать её похожей на округлённую линию). Закругленные края пересекались, образуя стыки. Выглядело довольно хорошо:

image

… но, как вы видите, получилось довольно ярко. И, по мере уменьшения молнии, яркость только увеличивалась (так как пересечения становились всё ближе). При попытки уменьшить яркость возникала другая проблема — переходы становились очень заметными, как небольшие точки на протяжение всей молнии.
Если у вас есть возможность рендерить молнию на закадровом буфере — вы можете отрендерить её, применяя максимальное смешивание (D3DBLENDOP_MAX) к закадровому буферу, а затем просто добавить полученное на основной экран. Это позволит избежать описанную выше проблема. Если у вас нет такой возможности — вы можете создать вершину, вырезанную из молнии путём создания двух вершин для каждой точки молнии и перемещения каждой из них в направлении 2D нормали (нормаль — перпендикуляр к среднему направлению между двумя сегментами, идущими в эту вершину).

Должно получится примерно следующее:

image

Анимируем

А это самое интересное. Как нам анимировать эту штуку?

Немного поэкспериментировав, я нашёл полезным следующее:

Каждая молния — на самом деле две молнии за раз. В этом случае, каждую 1/3 секунды, одна из молний заканчивается, а цикл каждой молнии составляет 1/6 секунды. С 60 FPS получится так:

  • Фрейм 0: Молния1 генерируется с полной яркостью
  • Фрейм 10: Молния1 генерируется с частичной яркостью, молния2 генерируется с полной яркостью
  • Фрейм 20: Новая молния1 генерируется с полной яркостью, молния2 генерируется с частичной яркостью
  • Фрейм 30: Новая молния2 генерируется с полной яркостью, молния1 генерируется с частичной яркостью
  • Фрейм 40: Новая молния1 генерируется с полной яркостью, молния2 генерируется с частичной яркостью
  • И т. д.

Т. е. они чередуются. Конечно, простое статическое затухание выглядит не очень, поэтому каждый фрейм есть смысл сдвигать немного каждую точку (особенно круто выглядит сдвигать конечные точки сильнее — это делает всё более динамичным). В результате получаем:

И, конечно, вы можете сдвигать конечные точки… скажем, если вы целитесь по движущимся целям:

И это всё! Как вы видите — сделать круто выглядящую молнию не так и сложно.

Автор: bak

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js