Давайте исследуем одну загадку: как Bethesda смогла сделать такие грибообразные облака ядерных взрывов?
Источник: Fallout 4
Реальная жизнь
Почему грибообразные облака сложно создавать в играх? Давайте рассмотрим пример из реальной жизни:
Источник: NUKE TESTS “The mushroom cloud of the atomic bomb”
Верхняя часть такого взрыва состоит из сложного движения: горячие газы «вращаются» вокруг центра, образуя «бублик»:
Кроме того, существует переход от состояния «только горячий газ» к состоянию «только чёрный дым»:
Как вы видите, здесь есть сложность, и ниже я вкратце расскажу, как разработчики пытались воссоздать эту картинку в прошлом.
Частицы
В некоторых старых играх не показывается целиком процесс образования облака взрыва, а вместо этого используется яркий полноэкранный эффект, после завершения которого частицы уже образуют типичное древообразную фигуру, но больше практически никакого движения не происходит. Вот пример из Mercenaries 2: World in Flames
Источник: matthythedavian
На самом деле, не так просто реализовать это при помощи обычных частиц и без дополнительных функций, например, анимации геометрии или текстур. Но уже получилось реализовать форму облака! Чтобы сделать движение чуть интереснее, мы можем вращать частицы с разной скоростью и направлением в зависимости от начальной точки эффектов:
Вот пример (R.U.S.E.), в котором это работает очень хорошо; он показывает красивое движение в поднимающихся облаках:
Источник: matthythedavian:
Но даже здесь горячие газы не «засасывает» снова в нижней части «пончика», как это происходит в реальной жизни. Именно поэтому в дополнение к частицам может потребоваться применение геометрии.
UV-анимация
На мой взгляд, Fallout 3 отлично справилась с правильной реализацией ядерного гриба с помощью геометрии и частиц (по крайней мере, для своего времени). Сразу после начала мы видим подробности движения, потому что взрыв не скрыт полностью огромной белой вспышкой:
Источник: Fallout 3
Всё это сделано при помощи UV-анимации, то есть, по сути, движением текстуры по геометрии. Если менять усиливать и уменьшать яркость этой текстуры (в начале и конце эффекта), а также добавить дым в виде частиц, то выглядит это вполне неплохо.
Источник: Fallout 3
Вот пример того, как Fallout-Texture выглядит на тестовой геометрии с аддитивным смешением и цветами вершин, чтобы избежать резких краёв по границам геометрии:
Источник: Fallout 3
В Fallout 4 похожая анимация текстур была использовала на частицах (а не на геометрии, как в Fallout 3), что выглядит очень красиво: одна текстура применена как форма облака, а другая тайловая текстура перемещается по частице. Это даже выглядит так, как будто точки направления скроллинга всегда направлены в центр взрыва:
Источник: Fallout 4
Если присмотреться, то можно заметить, что это «просто» текстура, движущаяся по плоской частице. Здесь я изменил вторую текстуру, чтобы механика была видна чуть лучше:
Источник: Fallout 4
Теперь у нас есть не только хорошая древообразная форма, но и очень красивое движение. Единственное, чего не хватает — это превращения горящего газа в чёрный дым (более сложным образом, чем затемнение ярких частей и оставление только чёрных частиц).
Я бы понятия не имел, как отрендерить взрывы в игре иначе, кроме как сложной программной симуляции, но потом я увидел трейлер с потрясающим взрывом:
Источник: Battlefield 4 Trailer
Разве не здорово, что огонь «поглощается» чёрным дымом, растворяется в нём? К сожалению, у меня нет этой игры и я не могу провести расследование, но, по крайней мере, я увидел, что это возможно. Но как?
Анимация текстур
Моё единственное объяснение заключается в том, что текстуры каким-то образом были пререндерены заранее, например, с помощью FumeFx. Вот пример того, что можно симулировать при помощи этого ПО (но, разумеется не в реальном времени):
Источник: FumeFx
И действительно, некоторые студии, например, Epic используют такой подход и покадрово запекают результат симуляции в текстуры. Если вы хотите больше узнать об этом процессе, то посмотрите замечательное видео:
Вернёмся к исходному вопросу: пока я уверен, что в Fallout 4 сделано именно это — пререндеринг взрыва и демонстрация его «воспроизведения» в игре. Напомню, что взрыв в Fallout выглядит так:
Источник: Fallout 4
Решение
Поэтому я поискал текстуры, надеясь найти текстурный атлас, состоящий из кадров взрыва, но нашёл лишь это:
Источник: Fallout 4 / SmokeCrawl01_Anim_d.dds
Это действительно текстурный атлас, и при воспроизведении он выглядит великолепно, к тому же его можно зациклить!
Источник: Fallout 4
Но в нём совсем нет пламени! Я думал, что чего-то не хватает, но потом вспомнил о концепции gradient mapping. Подробнее о ней можно прочитать в статье, а также посмотреть этот короткий пример:
Итак, мы можем раскрасить серую текстуру дыма в цветную, переопределив цвета. Здесь я переопределил чёрные значения как оранжевые, а то, что было белым, стало чёрным:
Источник: Fallout 4
Выглядит отлично, но есть ещё две проблемы:
-
Соотношение огня и дыма остаётся одинаковым, но у нас очень много огня в начале и только чёрный дым в конце
-
Мы не видим никакого силуэта облака, а если применим информацию о прозрачности из текстурного атласа (альфа-канала), то это будет выглядеть так:
Источник: Fallout 4
Сначала я был сбит с толку, потому что альфа-канал делает всю текстуру прозрачной почти на 50%, но в игре облака почти непрозрачные.
Похоже, решение снова заключается в gradient mapping: для вырезания формы облака разработчики используют альфа-канал текстурного атласа, но переопределяют его значения при помощи gradient mapping, получая непрозрачный результат:
Источник: Fallout 4
Fallout хранит свои градиенты в... текстурах! Вот так сюрприз. Давайте взглянем на это:
Источник: Fallout 4 SmokeCrawlGasFireGrad.dds
Что это такое? Для стандартного gradient mapping нам бы понадобилась текстура высотой всего 1 пиксель, так почему разработчики использовали квадратную текстуру? Причина в том, что они сэмплируют разные «строки» пикселей (снизу вверх), в зависимости от времени жизни частицы!
Это гениально! Благодаря этому можно раскрасить дым большим количеством огня вначале, и чем дольше частица поднимается в воздух, тем чернее она становится.
Источник: Fallout 4
Разработчики даже немного изменяют форму облака, модифицируя в зависимости от времени альфа-канал.
Источник: Fallout 4
Это потрясающе! Мне очень нравится, что они сделали зацикленную анимацию, ведь это позволяет задать очень большой срок жизни частицы без необходимости запекания в атлас дополнительных кадров, потому что варьирование цветов обрабатывается отдельно.
Думаю, можно уверенно сказать:
Загадка решена!
Если вам любопытно, я подготовил ещё несколько экспериментов, чтобы лучше понять систему и сделать заметки о своих наблюдениях.
Здесь я раскрасил нижнюю и верхнюю часть текстуры градиента в разные цвета; как и ожидалось, на определённом этапе срока жизни частиц цвет резко меняется:
Источник: Fallout 4
Здесь я сделал то же самое, но с разными цветами слева и справа, то есть один цвет перезаписывает значения градаций серого от 0 до 127, а другой — от 128 до 255:
Источник: Fallout 4
Этот эксперимент оказался немного странным: я поменял альфа-канал текстуры градиента (всё стало чёрным, за исключением одной строки пикселей внизу). Я бы ожидал, что текстуры пропадут очень рано. Но посмотрите на результат сами:
Источник: Fallout 4
Цвет меняется как и раньше (то есть шейдер выполняет считывание из центральной позиции канала diffuse, где происходит изменение цвета), но частица по-прежнему видна! Предположу, что шейдер обрабатывает альфа-канал иным, нелинейным образом и дольше остаётся на первой строке пикселей.
В своём последнем примере я сделал так, чтобы альфа-канал видимой текстуры скрывался всё больше и больше в процессе жизни облака:
Источник: Fallout 4
Дополнение 1
Ravendarke показал примеры из игры, над которой он работает (Wayward Terran Frontier), где нечто подобное выполняется интересным образом для того, чтобы текстура со швом казалась бесшовной (подробнее об этом ниже):
Источник: Wayward Terran Frontier
Варьирование цвета выполняется как в Fallout 4. Текстура дыма просто имеет серый цвет.
Источник: Wayward Terran Frontier
Текстура дыма в игре состоит из 14 кадров, но первый и последний не совпадают идеально. Эта проблема была решена так: вместо покадрового воспроизведения анимации кадры смешиваются друг в друга:
Источник: Wayward Terran Frontier
Дополнение 2
Froyok опубликовал в Twitter отличную статью о частицах + модификации цветов, написанную Alkemi.
Источник: Alkemi
Также он упомянул обсуждение, в котором кто-то задал вопрос, как реализовали взрыв в Battlefield 4.
Дополнение 3
Флориан Смолка показал свою работу над шейдером для игры отличного немецкого разработчика Mimimi Productions! Мы можем даже взглянуть на текстуры и код шейдера с комментариями! Разве не здорово?
Дополнение 4
Золтан Эрдокови объясняет в своём посте свою методику добавления движения в этот дым без покадровой текстуры. Особенно потрясающе выглядит огненное ядро!
Источник: Drone Alone
Дополнение 5
Я нашёл видео, в котором игрок подошёл очень близко к грибу из Fallout 4, и это позволило нам лучше рассмотреть, как оно сделано.
Источник: Fallout 4: GOING TO THE NUKE Before it Kills You
Дополнение 6
«Nebbul vfx» опубликовал на своём Youtube-канале видео о комбинировании градиентов Fallout 4 с векторами движения. Выглядит фантастически!
Дополнение 7
Pawige опубликовал потрясающий анализ созданного им пиксельного взрыва!
Дополнение 8
thwix поделился в Discord realtime vfx отличным трюком для добавления «скорректированного для камеры» движения частиц ударной волны, но его можно использовать и для добавления красивого движения вращения, подобного описанному в моей статье. Цитата:
«Для имитации несжимаемости в динамике жидкостей часто пригождаются частицы, вращающиеся противоположно их вектору скорости»
«но при просмотре под другим углом картинка начинает ломаться из-за того самой природы спрайтов, вращающихся относительно камеры»
«забавное решение этой проблемы заключается в том, чтобы менять вращение на основании отношения между углом обзора и вектором скорости»
«Так мы получаем внешне одинаковое вращение с обоих углов»
«Это также очень подходит для эффектов ударной волны»
Автор: PatientZero