Реализация тумана войны (3/3)

В прошлых частях мы в общем-то уже все сделали. И на самом деле сегодняшняя часть совсем не обязательна, но мне интересно описывать реальные случаи, а с Tanks vs Aliens все обстояло именно так.

Новая задача

Если вы уже поиграли в Tanks vs Aliens (она вышла буквально на днях, ура!), то могли заметить, что уровни в ней не имеют широких полей: ключевые объекты могут размещаться довольно близко к краю карты. Почему так получилось и почему было принято решение сделать именно так — отдельный вопрос. Сейчас же нас интересует как сделать плавный переход в пустоту на границах карты.

Не знаю как вам, но мне сразу показалось, что это имеет отношение к туману войны. Судите сами: нужно добавить попиксельное затенение объектов, зависящее от положения в плоскости XZ.

Именно поэтому я решил добавить эту возможность в существующий туман войны. Простое и быстрое решение для проверки идеи:

И вот такой результат:

Вроде бы все ок. Но… слишком искусственно? Давайте добавим рваные края:

_MapBorderNoiseTex – полоска шума в 1 пиксель толщиной. Вот как это выглядит:

Видите, что ноги девушки будто обрублены снизу? На самом деле вся картинка обрезается леттербоксингом. Но в месте, где видно фон, это выглядит странно. Решение? Сделаем фон светлее.

Новые сложности

И именно с этой маленькой безобидной фичей что-то пошло не так. Сделать пиксель черным – тривиально. А вот сделать его серым, с учетом того разнообразия моделей освещения и рендеринга, о которых я писал ранее оказалось не так просто.

В чем, собственно, сложность? Изначально задача шейдера тумана войны формулировалась так: color *= lerp(FowBrigtness, 1, fov). То есть, для полностью сокрытого пикселя домножаем его цвет на FowBrightness (скажем, 0.5), для полностью видимого пикселя — на 1, т.е. не меняем его. Теперь же нам потребовалось кое-что покруче: color = lerp(LevelBackground, color, border). То есть, для пикселя за границами карты взять LevelBackground (серый), для пикселя в пределах карты — его исходный цвет, ну а для промежуточных — что-то среднее.

Принципиальная разница между этими двумя задачами в том, что в первом случае мы не меняем оттенок пикселя, а во втором — меняем. Более конкретно: мы хотим, чтобы все материалы во всех моделях освещения выдавали именно тот серый, который мы задали. К сожалению, на surface shaders сделать такое мне не удалось.

Может все же пост-эффект?

В процессе борьбы с этой трудностью, меня все чаще стала посещать мысль: а, может, все же сделать пост-эффектом? Тогда красить фон можно будет как угодно. А что касается прозрачности, так вышло, что туман войны до сих пор не распространялся на прозрачные объекты. И это полностью устраивало. Потому что из прозрачных у нас были разве что частицы — выстрелы и взрывы, а они обычно возникают там, где игрок видит.

И вот я решил переделать все на пост-эффект. Сделать это получилось быстрее, чем я ожидал. Самым приятным в этом занятии было удаление вкраплений тумана войны из всех шейдеров проекта. Теперь-то туман войны стал по-настоящему автономен!

В таком переходе всплыл один сложный момент: в шейдере эффекта нужно получить позицию пикселя в мировом пространстве. Для этого следует воспользоваться буфером глубины. Но вот как именно это сделать… как получить к нему доступ из шейдера и в каком формате он сэмплится — эту информацию оказалось не так уж просто найти. В итоге я подглядел решение в одном из пост-эффектов из Standard Assets и сделал так же.

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

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

В примере выше можете обратить внимание на занятный трюк, также позаимствованный у Unity: использование z-координаты вершины в качестве индекса в _FrustumCorners. Для отрисовки такого специально подготовленного квада можно применить такую функцию:

Используется по аналогии со стандартным Graphics.Blit(). Да, кстати, как вы могли догадаться, я использую один эффект и для тумана войны, и границ карты. Сделано это в угоду оптимизации. Я бы, конечно, лучше разделил их, но это даст 2 прохода вместо одного. Да, есть Unity Postprocessing, но я еще не успел освоить его и не хотел заниматься этим прямо сейчас.

Это уже пост-эффект версия. Если откроете в полном размере, то обязательно заметите, что между фоном и черными полосами есть заметная разница.

И, что забавно, на прозрачные объекты туман войны все же распространяется. Но в экранном пространстве. Т.е. костер на земле будет затеняться примерно правильно, а костер в воздухе уже начнет обрубаться. Но в нашем случае это совершенно не важно.

One thought on “Реализация тумана войны (3/3)

  1. “Самым приятным в этом занятии было удаление вкраплений тумана войны из всех шейдеров проекта” до этого момента все же висел вопрос “а как быть со стандартными шейдерами?” но теперь он отпал 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *