Молнии

Автор оригинала: Josh Jersild
  • Перевод


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

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

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

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


Как оказалось, генерация молнии между двумя точками может быть на удивление простой задачей. Она может быть сгенерирована как 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 генерируется с частичной яркостью
  • И т. д.


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



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



И это всё! Как вы видите — сделать круто выглядящую молнию не так и сложно.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 22

    +9
    Положил в копилку. Очень эффектная штука, а делается так просто — супер!
      0
      А почему молния — желтая?
        +17
        А почему бы и нет? Вот, например, kreosan самые что ни на есть желтые разряды из микроволновки вытягивает:

        0
        а почему бы и нет? не все ж делать стандартно — синенькое. Да и в жизни можно увидеть такое в желтом спектре.
          +1
          Я думаю цвет зависит от материалов разрядников и окружающей среды.
          0
          Прочитал несколько раз, но не нашел подробностей про то, откуда берется сам меш для молнии
          Первоначально система, разработанная для игры использовала закруглённые лучи. Каждый сегмент молнии рендерился с использованием трёх четырёхугольников, для каждого из которых применялась текстура со светом

          Треугольники генерировались каждый кадр? При перемещении конечных точек пересчитывается вся молния? И что подразумевается под «генерацией» молнии в вашем конечном списке — полная «перегенерация» меша?
            0
            Я не автор исходного текста, но попробую ответить исходя из своего понимания.
            Изначально автор не делал треугольники (те что на иллюстрации ниже) а взял прямоугольник сегмента молнии, разбил на три части и заполнил получившиеся четырёхугольники текстурой.
            Треугольники генерировались каждый кадр? При перемещении конечных точек пересчитывается вся молния?

            Насколько я полнял, каждый раз при сдвиге молнии необходимо её перегенерация. Но, кажется, что это относительно дешёвая операция.
              +2
              Мне больше интресна генерация с точки зрения выделения памяти под новый меш / новые вершины, треугольники и нормали.
              Немного уточню:
              Изначально автор не делал треугольники (те что на иллюстрации ниже) а взял прямоугольник сегмента молнии, разбил на три части и заполнил получившиеся четырёхугольники текстурой.
              меши с текстурой в любом случае представляют собой набор треугольников, из которых состоят прямоугольники. Соответственно для «заполнения прямоугольника текстурой» необходимо определить треугольники, из которых состоит этот прямоугольник и соответствующие им вершины, которым еще нужно задать правильные UV координаты и нормали.

              Вот меня как раз интересует этот процесс генерации набора вершин, нормалей, треугольников и т.д., есть ли какой-то механизм определения, когда нужны новые вершины, или каждый раз происходит генерация нового набора? В общем хотелось бы узнать более низкоуровневые подробности генерации и рендера
                +1
                Мне честно говоря не понятно, что именно вам не понятно. Очевидно что да, перегенерируются. Рассчитываются координаты новых вершин, и эти вершины заливаются в VertexBuffer.
                есть ли какой-то механизм определения, когда нужны новые вершины
                У VertexBuffer-а есть размер. Если размера недостаточно, то нужно аллоцировать новый. Это механизм типа: if (vbo->size < newsize) { //аллоцируем новый буфер }.
            0
            Люблю космические симуляторы вроде starscape. Как называется ваша игра? И если есть аналоги/конкуренты — поделитесь? :)

            По теме — выглядит красиво, но странно, когда молния бьет на 105*, а потом на 0*(третья секунда)
              0
              Оригинал статьи: drilian.com/2009/02/25/lightning-bolts/
              Наткнулся на нее полгода назад, когда хотел сделать молнии в своей игре. Получилось симпатично )

              В этом же блоге автор пишет: «Valve recently announced that Procyon is in the most-recent batch of titles to be given the green light for release on Steam!».
              +2
              Молнии отличные получились. Очень реалистично! Правда, взрывы не очень. Рекомендую что-то с ними сделать.

              Что касается реальных ударов молнии — то вот детали высокоскоростной съемки: фрагмент документального фильма.
              0
              Добрый день. А что здесь имеется ввиду под «текстурой со светом»? Как она хоть примерно должна выглядеть? Как добиться свечения?
                0
                Не знаю точно что имел автор, но, скорее всего — что-то вроде обычного градиента шириной в один пиксель и некоторой высотой. В центре ярко, по краям прозрачно.
                  +1
                  Спасибо. Поискал в источнике — и там в комментариях есть такой же вопрос. Ответ автора — радиальный градиент, прозрачный к краю. Накладывается на меш определенным образом, получается скругление концов прямоугольников.
                    +1
                    На самом деле в данном случае достаточно текстурные координаты хранить в диапазоне [-1;1], и тогда никакая текстура не нужна. Считать интенсивность можно через как float Intensity = max(0.0, 1.0 — length(In.TexCoord));
                  0
                  с текстурой должен работать соответствующий Shader.
                    +1
                    Да, и текстура должна быть соответствующая. Умиляют такие комментарии.
                  +3
                  Что-то я, кажется, с альфа-каналами перемудрил

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

                  Самое читаемое