А как всё мило начиналось
Параллакс для сколлинговых игр дело обычное, однако степень проработки параллакса разительно отличается от игры к игре. Получить действительно красивый и интересный фон довольно сложно. Многие сейчас переходят на 3D и параллакс как эффект вообще отпадает, так как получается естественным путём.
В былые времена на фоне пускали точки с эффектом параллакса (звезды), чуть позже стали делать фоны в пару-тройку разных слоёв. А сейчас уже не обойтись парой слоёв или скроллирующимся фоном, нужно немножко больше.
Lightforce, Commodore 64
Внимание, под катом большие картинки.
Мощности современных компьютеров (да и мобильных устройств) растут день ото дня, эффекты же зачастую остаются довольно простыми. Давайте попробуем разобраться в чем же проблема: малая мощность оборудования, сложность создания комплексных фонов, лень… Возможно, что это всего лишь недостаточная проработка? Отсутствие внимания к деталям?
Проверить это было легче всего, попробовав сделать это самому. Поборов лень, я начал делать. Так как художник из меня никакой, то прошу сразу извинить меня за то, что враги у меня абстрактны (зеркальные сферы), задний план сгенерирован шумом перлина, а единственный нарисованный кораблик принадлежит перу моего старого знакомого Рикардо, и я крайне надеюсь что он не против такого расклада.
Оценить результат можно по видео:
Скриншоты по ходу статьи немного отличаются от видео, по сути в них изменен только фон, на чуточку более симпатичный.
Параллакс
Суть эффекта параллакса заключается в том, что скорость движения (и размер) объектов зависит от их удалённости от наблюдателя — чем объект дальше, тем медленнее он движется относительно наблюдателя и тем меньше он в размере. Разумеется, это справедливо лишь для неподвижных объектов, либо движущихся с одной скоростью (что в целом равноценно). Таким образом, мы можем принять за аксиому тот факт, что наш самый дальний план не будет двигаться вовсе. Или будет двигаться очень незначительно. Второй вариант даст нам лучшее ощущение передвижения, в то время как первый позволит показать безумно удалённые объекты, такие как звезды.
От теории к практике
Чтобы всё сказанное мной в этом топике не казалось лишь голой теорией, я буду снабжать все свои выкладки скриншотами из приложения, которое исполняется на моем интегрированном видео от Intel (HD2000) с минимум 60fps. Так как игру (точнее будет сказать прототип) я написал до написания статьи, в кадр попадают очки, фпс, шкала здоровья. Приношу свои извинения, заметил не сразу, поэтому решил оставить на скриншотах. Сути дела они не меняют.
Дальний план
Действие происходит в космосе, поэтому дальним планом станут звезды и туманности. Разумеется, я не хочу, чтобы игру сравнивали с фотографиями NASA, поэтому туманности яркие. Для создания красочного псевдо космического фона я воспользовался утилитой FilterForge 3.0. Она имеет пробный 30-дневный период и её может пощупать любой желающий.
Также на дальний план я поместил звезду, которая находится достаточно близко, чтобы стать самым ярким объектом во всей сцене, но и достаточно далеко, чтобы двигаться со скоростью дальнего плана.
Средний план
Средний план носит больше декоративный вид, так как он еще не основной, но уже привлекающий внимание. Поэтому он не должен быть мёртвым. Он должен жить, там должно происходить действие, пусть даже самое примитивное. Для оживления плана я возьму астероиды и огромные космические фермы. Астероиды все одинаковые, но для заднего плана сейчас это не самое важное. Даже их размытость пойдет на пользу, чтобы они не так сильно отвлекали.
Фермы были нарисованы мной в фотошопе за пару минут, астероиды честно взяты с просторов интернета.
Чтобы не было пусто, добавлю пыли. В идеале объектов, конечно, должно быть существенно больше. Другие корабли, планеты, станции, метеоры… Иными словами всё, что придет на ум.
Главный план
Главный план это игра, поэтому ничего кроме игровых объектов тут не будет. Чтобы мне было легче не умирать и делать скриншоты, врагов я отключил, поэтому эффектов взрывов и врагов, увы, нет.
Передний план
Да, это не ошибка, ближе главного плана будет еще передний план. Чтобы он не мешал слишком сильно и не пугал игрока, он будет размыт, как бы не в фокусе. Всё, что на нём будет, это всякий мусор. Тем не менее в игре передний план будет играть еще и роль дополнительного усложнения, поскольку будет скрывать часть основного, игрового плана.
Мусор
Точно, мусор! Совсем забыл. Надо добавить мусор, причем преимущественно в основной, игровой план (совсем мелкий, чтобы не представлять визуальной опасности). Отсутствие объектов в основном плане напрочь запутает игрока. Игрок потеряет ощущение глубины и забудет в каком из планов происходит действие. Мусор также может быть в любом из вышеперечисленных планов.
Так как делал я эту игру на конкурс и сроки были крайне сжатыми. На всё, про всё была неделя и от многих фич пришлось отказаться. Мусор попал туда же, под нож.
Итак
Что же я сделал не так? Вообщем-то всё верно, но смотрится ужасно! Ещё нужно навести красоту и убирать косяки. Для начала распределим объекты по яркости, и вообще приглушим их, чтобы не выпадали из сцены.
Контраст остался большим, однако уже таких явных перепадов цвета не заметно. Самые внимательные читатели обратили внимание, что солнце рисуется неверно. Оно сзади сцены, а должно быть спереди и перекрываться только игровыми объектами и передним планом. Исправляем.
Теперь я добавлю всего один недостающий штрих…
Вот так намного лучше. А ведь это только один постпроцесс шейдер. Шейдер объемного света. Как же он работает?
Как же он работает?
Исходным изображением для формирования такого кадра является, как бы это ни было странно, предыдущее изображение. Однако есть еще кое-что. Сформированный кадр мы отложим в сторону и специально для достижение эффекта подготовим дополнительную текстуру, содержащую информацию о свете и перекрывающих его объектах. Выглядит она вот так:
В эту текстуру мы рендерим источник света и всё, что может загораживать свет. И чем темнее мы это рисуем, тем больше оно загораживает свет. Источником света в данном кадре служит центр экрана (координаты источника дополнительно передаются шейдеру во время отрисовки).
Обратите внимание, что сцена 1в1 повторяет ту, что мы видим по расположению и размеру объектов:
Сверху текстура для лучей, снизу обычная отрисовка
Чтобы эту текстуру преобразовать в лучики объемного света, мы должны обработать её шейдером, код которого, я приложу в конце статьи. Результат очень напоминает эффект radial blur из photoshop, коим по сути и является с поправкой на коорданаты источника света.
Вот и получились наши лучики света. Всё, что теперь осталось сделать, наложить эту текстуру поверх всей нашей сцены с аддитивным добавлением, смешать их. Финальный результат был чуть выше, а чтобы увидеть разницу, я наложу эффект только на половину экрана:
Слева с лучами объемного света, справа без
Вместо резюме
Планы расписал. Их всего четыре, но так как объекты в одном плане могут двигаться с разной скоростью, это создаст эффект непрерывного пространства. Всё, что нужно для этого — варьировать их скорость и размер. Для придания пущей красоты и объёма — эффект объемного света.
Я не исключаю использование дополнительных эффектов, вроде смазывания при движении или световой аберрации. Так как целью было создание игры, работающей при 60fps на встроенном в процессор видео ноутбука, от подобных усложнений пришлось отказаться. Надо сознаться сразу, что при создании параллакса требовалось даже 60, а все 120 кадров в секунду, так как последующая реализация самой игры снизит производительность. Также надо учесть и разрешение — 1280*720, накладывающее ограничения по скорости обработки полноэкранных постпроцесс эффектов.
Инструментарий
Инструментами для разработки я пользовался привычными для меня, а именно: собственный фреймворк под Direct3D9, HLSL и компилятор от Microsoft для создания бинарного кода эффектов. Основной код написан на Embarcadero Delphi XE3. Текстуру фона я отрендерил в FilterForge 3.0.
Шейдер
sampler2D DiffuseMap : register(s0);
float2 LightPos : register(c0);
float4 Params : register(c1);
float4 std_PS(vertexOutput Input) : COLOR {
float4 Output;
int NumSamples = 20;
float Weight = 1.0;
float Density = 0.5;
float Exposure = 0.25;
float2 DeltaTexCoord = (Input.TexCoord - LightPos);
DeltaTexCoord *= 1.0 / NumSamples * Density;
float3 Diff = tex2D(DiffuseMap, Input.TexCoord);
float illuminationDecay = 1.0;
for (int i = 0; i < NumSamples; i++)
{
Input.TexCoord -= DeltaTexCoord;
float3 sample = tex2D(DiffuseMap, Input.TexCoord);
sample *= illuminationDecay * Weight;
Diff += sample;
illuminationDecay *= 0.9;//Decay;
}
Output = float4(Diff * Exposure, 1);
return Output;
}
Автор: Darthman