Часть №8: Создание системы частиц в игре Breakout на C++/OpenGL

  Дмитрий Бушуев  | 

  Обновл. 26 Ноя 2020  | 

 385

Частица (англ. «particle») в OpenGL — это крошечный 2D-прямоугольник, который своей лицевой стороной всегда обращен к камере и (обычно) содержит текстуру, значительная часть которой является прозрачной. Частица сама по себе фактически является обыкновенным спрайтом. Объединяя сотни или даже тысячи подобных частиц, мы можем получить самые разнообразные и удивительные эффекты.

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

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

Глядя на пример с огнем можно заметить, что генератор, вероятно, порождает каждую свою частицу в точке, близкой к точке расположения самого генератора, и с направленным вверх вектором скорости. Также можно предположить, что у него есть 3 различные области, поэтому, судя по всему, одним частицам придается более высокая скорость, а другим — более низкая. Вдобавок, видно, что чем больше значение y-координаты частицы, тем менее желтым или ярким становится её цвет. После того, как частицы достигают определенной высоты, время их жизни исчерпывается, и они погибают; никогда не достигая звезд.

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

Заметно, как генератор частиц порождает каждую свою частицу в точке текущего положения мяча, придает ей некоторую скорость, и изменяет цвет частицы в зависимости от оставшегося времени её жизни.

Для рендеринга частиц мы будем использовать новый набор шейдеров:

Фрагментный шейдер:

Мы определяем стандартный набор атрибутов частицы, состоящий из координат её местоположения и соответствующих текстурных координат, а также переменную смещения offset и uniform-переменную цвета color, для того, чтобы влиять на выходной результат каждой частицы. Обратите внимание, что в вершинном шейдере задающий размеры частицы прямоугольник умножается на 10.0f; вы также можете определить переменную scale как uniform-переменную, тем самым получая возможность индивидуально контролировать размеры каждой частицы.

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

Затем в каждом кадре мы порождаем несколько новых частиц с предопределенными начальными значениями. Для каждой частицы, которая (все еще) жива, мы также обновляем эти значения:

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

Поэтому, необходимо найти первую «отжившую свое» частицу (время жизни < 0.0f) и обновить её в виде новой возрожденной частицы.

Функция FirstUnusedParticle() пытается найти первую «отжившую» частицу и возвращает её индекс:

Функция сохраняет найденный индекс последней отжившей частицы. Поскольку следующая «отжившая» частица, скорее всего, будет сразу после сохраненного индекса, то мы сначала ищем по данному индексу. Если мы не нашли мертвых частиц первым способом, то далее просто выполняем более медленный линейный поиск. Если ни одна из частиц не является отжившей, то возвращается индекс 0, что приведет к перезаписи первой частицы. Обратите внимание, что если функция достигает последнего случая, это означает, что ваши частицы живут слишком долго; вам нужно будет порождать меньше частиц за кадр и/или резервировать большее количество частиц.

Затем, как только первая «отжившая свое» частица найдена в списке, мы обновляем её значения, вызывая функцию RespawnParticle(), которая в качестве своих параметров принимает ссылку на саму частицу, ссылку на объект типа GameObject и вектор смещения:

Данная функция просто сбрасывает время жизни частицы до 1.0f, задает случайное значение (от 0.5f и выше) яркости (через цветовой вектор), а также присваивает случайные значения положения и скорости в зависимости от данных объекта object.

Второй цикл в рамках функции обновления проходится по всем частицам и для каждой уменьшает время её жизни на переменную дельта-времени (dt); таким образом, время жизни каждой частицы в точности соответствует секунде (секундам), которую ей изначально разрешено прожить, умноженной на некоторый скаляр. Затем мы проверяем, жива ли частица, и если да, то обновляем её положение и цветовые атрибуты. Мы также медленно уменьшаем альфа-компонент каждой частицы, в результате чего создается впечатление, что они медленно исчезают с течением времени.

Остается только визуализировать частицы:

В вышеприведенном фрагменте кода мы для каждой частицы задаем смещение и значение uniform-переменной цвета, связываем текстуру и визуализируем 2D-прямоугольник. Что интересно здесь отметить, так это два вызова функции glBlendFunc(). При рендеринге частиц вместо заданного по умолчанию режима смешивания GL_ONE_MINUS_SRC_ALPHA мы используем (аддитивный) режим GL_ONE, который придает частицам очень аккуратный эффект свечения. Этот режим является наиболее предпочтительным режимом смешивания для сцены (из начала данного урока) с визуализацией огня, так как «свечение» огня сильнее непосредственно в центре пламени, где находится большинство частиц.

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

#Класс ParticleGenerator

Заголовочный файл particle_generator.h:

Файл реализации particle_generator.cpp:

В коде игры мы создаем генератор частиц и инициализируем его с помощью следующей текстуры:

Код:

Затем мы изменим функцию Game::Update(), добавив вызов функции Particles->Update() генератора частиц:

В результате этого, каждый кадр будут порождаться по 2 частицы, и их положение будет смещено к центру мяча. Последний шаг — это рендеринг частиц:

Обратите внимание, что сначала мы визуализируем частицы, а уже после них — мяч. Таким образом, частицы в конечном итоге оказываются впереди всех других объектов, но позади мяча.

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

  GitHub / Часть №8: Создание системы частиц в игре Breakout на C++/OpenGL — Исходный код

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (2 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *