Как сделать туннель в юнити

Добавил пользователь Алексей Ф.
Обновлено: 05.10.2024

Что предлагает community

После двух дней бесполезного пота и алгоритмистики (Плоскость явно не была предназначена для использования в таких целях), я таки начал делать ландшафт с использованием Terrain. Должен сказать, что среди отдельных членов сообщества Unity, ходила идея создания динамического ландшафта для своей игры. Некоторые люди задавали на форумах вопросы и получали ответы. Им рекомендовали использовать метод SetHeights, принимающий на вход кусок нормализованного от 0f до 1f heightmap'а, который будет установлен начиная с точки (xBase; yBase) на выбранный ландшафт.

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

Сама по себе деформирующая часть была до неприличия простая.

Объект DeformationData содержал в себе координаты X и Y, к которым нужно применить деформацию, нормализованный heightmap, который аддитивно либо мультипликативно накладывался на текущий ландшафт и прочий boilerplate, необходимый для работы механизма деформаций.

Также был генератор деформаций, позволяющий, например,

И все это было основой всего Tech Demo, если так конечно можно выразиться.

Анализ результатов первой попытки

Если вы посмотрели Tech Demo, то, вероятно, сразу же заметили определенные проблемы в механизме деформаций. Если же вы не смотрели его (за что я вас не виню), то я вам расскажу что было не так. Основной проблемой была производительность. Точнее ее полное отсутствие. Когда начинались деформации, framerate падал до очень маленьких (на некоторых машинах однозначных) чисел, что было неприемлемо, потому как никакой графики в игре по сути не было. Мне удалось выяснить, что сам по себе метод SetHeights() вызывает сложнейшую череду расчетов LOD для ландшафта и по этой причине не годится для деформаций ландшафта в реальном времени. Казалось бы, мои надежды рухнули и реализация деформаций в реальном времени на Unity невозможна, но я не сдавался и выяснил очевидную, но очень важную особенность механизма пересчета LOD.

Чем меньше разрешение карты высот ландшафта, тем меньше удар по производительности при применении SetHeights().

Разрешение карты высот — это параметр, характеризующий качество отображения ландшафта. Оно является целочисленным (очевидно), отчего в сниппетах выше для обозначения координаты на карте использовались целочисленные переменные. И оно может быть больше размеров ландшафта, например, для ландшафта 256х256 можно установить разрешение карты высот равным 513, что придаст ландшафту точности и менее угловатые очертания. Почему именно 513, а не 512, я расскажу в следующей секции.

Игры с разрешением карты высот позволили мне найти более-менее оптимальные размеры для моей конфигурации, но я был сильно огорчен результатами. Для успешного применения такого ландшафта в RTS, его размеры должны быть достаточно большими, чтобы на нем можно было сосуществовать некоторое время хотя бы двум игрокам. По моим первоначальным оценкам, карта размером 2х2км (или 2048х2048 Unity Units) должна была быть в самый раз. Для того, чтобы не замечать влияния на framerate деформаций в реальном времени, размер ландшафта должен был быть не более 512х512 единиц. Более того, одинарная точность карты высот давала не самые впечатляющие результаты, когда дело касалось визуального качества. Ландшафт был местами угловат и кривоват, что требовало удвоения точности карт высот.

В общем, дела обстояли не очень хорошо.

Super Terrain — концепт и теория

Первая проблема

Первая проблема была достаточно пустяковой: Я просто выбрал размер для chunk'а 256х256 с двойной точностью (heightmap resolution = 513). Такой setup не вызывал у проблем с производительностью на моей машине. Возможно в будущем пришлось бы пересмотреть размеры chunk'ов, но на текущем этапе, такое решение меня устраивало.

Вторая проблема



Третья проблема

Сама по себе идея алгоритма для универсального применения деформации состояла в следующем:

  1. Разделить heightmap деформации на девять частей по одной на каждый из chunk'ов, на который потенциально может повлиять деформация. Так центральная часть будет отвечать за деформацию chunk'а, на который непосредственно попала деформация, стороны отвечают за chunk'и, находящиеся слева/справа или сверху/снизу и т.д. В случае, если деформация не изменяет chunk, то ее составляющая будет равна null.
  2. Применить частичные heightmap'ы к соответствующим chunk'ам, либо проигнорировать изменения, если частичный heightmap равен null.

Super Terrain — практика

Создание SuperTerrain

Скажу сразу, что понадобилась эта возможность глобального применения карты высот потому как для создание ландшафта было процедурным, а для его использования использовался Square-Diamond алгоритм, выходом которого и была большая матрица float'ов — наша большая карта высот.

В целом, создание SuperTerrain — достаточно простой и интуитивно понятный процесс, описанный

Здесь же применяется лечение одной из проблем с черными полосами на границах ландшафта — метод SetNeighbours(), который итерируется по созданным ландшафтам и проставляет им соседей. Важное замечание: метод TerrainData.SetNeighbors() должен применяться для всех ландшафтов в группе. То есть, если вы указали, что ландшафт А является соседом сверху ландшафта В, то вам также нужно указать что ландшафт В является соседом снизу для ландшафта А. Эта избыточность не совсем понятна, однако она значительно упрощает итеративное применение метода, как в нашем случае.

В коде выше есть несколько интересных моментов, например — использование divisor'а при создании очередного ландшафта. Если честно, я и сам не понимаю, почему это нужно — просто создание ландшафта обычным способом (без divisor'а) создает ландшафт неправильного размера (что может быть багом, а может я просто плохо читал документацию). Данная поправка была получена эмпирически и до сих пор не подводила, поэтому я решил оставить ее как есть.

Вы также могли заметить, что внизу листинга имеются два подозрительных метода-хэлпера. На самом деле, это просто результат рефакторинга (так как я показываю листинги более-менее стабильной версии, которая прошла несколько рефакторингов, но все еще не идеальна). Эти методы используются и дальше, при применении локальных и глобальных деформаций. Из их названия несложно догадаться, что они делают.

Применение глобальной карты высот

Согласен, выглядит эта пара методов не очень красиво, но я постараюсь все объяснить. Итак, название метода SetGlobalHeightmap говорит само за себя. Все, что он делает — итерируется по всем chunk'ам (которые здесь названы subterrain'ами) и применяет к ним именно тот кусочек карты высот, который соответствует его координатам. Здесь и используется злополучный SetHeights, производительность которого и заставляет нас идти на все эти извращения. Как видно из кода, константа SuperTerrainHeightmapResolution не учитывает отличие на 1 разрешения карты высот от степени двойки (чье существование обосновывается в прошлой секции). И пусть вас не смутит ее название — эта константа хранит разрешение карты высот для chunk'а, а не для всего SuperTerrain. Поскольку код SuperTerrain активно использует различные константы, я сразу покажу вам класс GameplayConstants. Возможно, так будет понятнее, что же все таки происходит. Я убрал из данного класса все не относящееся к SuperTerrain.

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

Метод GetSubHeightMap используется и дальше при применении локальных деформаций, но об этом позже.

Применение локальных деформаций

Для применения деформаций нужна не только карта высот, но и прочая информация типа координат, способа применения, размеров и т.д. В данной версии вся информация инкапсулирована в класс TerrainDeformation, листинг которого можно увидеть

Несложно догадаться, что наследники этого класса реализуют абстрактный метод Generate(), где и описывают логику по созданию соответствующего heightmap'а для деформации. Также TerrainDeformation содержит информацию о том, как именно она должна применяться к текущему ландшафту — это определяет виртуальный метод ApplyToPoint. По-умолчанию он определяет деформацию как аддитивную, но перегрузив метод можно добиться более сложных методов комбинирования двух высот. Что касается разделения матрицы деформации на суб-матрицы и применение их к соответствующим chunk'ам, то этот код находится в классе SuperTerrain и выделен в

Как вы уже наверное догадались, единственный public метод, который есть в листинге — и есть самый главный. Метод ApplyDeformation() позволяет применить указанную деформацию к ландшафту в заданных координатах. Первым делом при его вызове происходит конвертация координат на ландшафте в координаты на карте высот (помните? Если размеры ландшафта отличаются от разрешения карты высот, то это нужно учесть). Вся работа по применению деформации происходит внутри девяти вызовов ApplyPartialHeightmap, которые применяют куски карты высот от деформации к соответствующим им chunk'ам. Как я уже говорил ранее, нам нужно именно девять частей, а не четыре чтобы учесть все возможные граничные и угловые случаи:


Именно этим делением и занимаются методы GetXXXSubmap() — получением необходимых миноров деформации на основании данных о положении деформации и границах различных chunk'ов. Каждый из методов возвращает null, в случае если деформация не имеет влияния на соответствующий chunk и метод по применению этих самых миноров (ApplyPartialHeightmap()) ничего не делает, если на вход ему приходит null.

Результаты и выводы

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

  • Тяжелую работу выполнять в отдельном процессе, чтобы снизить влияние на framerate в особенно интенсивных сценах.
  • Оптимизировать разбиение на миноры, избавившись от выделения памяти каждый раз, посредством, например, кеширования. Пока-что сложно представить как можно будет кэшировать что-то подобное. Для начала можно ограничиться самыми частыми кейсами — небольшая деформация прямо по середине chunk'а.
  • Добавить возможность влиять не только на геометрию ландшафта, но и на его текстуру — деформации с изменением splat-map'ов.
  • Оптимизации для применения нескольких дфеормаций за один кадр. Например, накапливать деформации для chunk'ов в каком-нибудь буфере и по окончанию обработки логики каким-либо образом их комбинировать и применять — получим один вызов SetHeights на chunk, даже если было несколько деформаций.

Маленькие, едва заметные деформации из-за множественных попаданий снарядов:



Один из chunk'ов выделен, деформации на стыках применяются верно, видимых разрывов нет:


Большой кратер, занимающий весь chunk.



И, разумеется, ссылки на играбельные демо:

Управление в Демо: Мышь + WASD = перемещение камеры. Колесико мыши = зум. Ctrl = поворачивать камеру.

Запущенная игра с управляемым персонажем

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

Я решил не заморачиваться с кодом. Так как я новичок, путевого ничего все равно не напишу. Следовательно, первое время буду использовать готовые ассеты. Устанавливать их можно из официального Asset Store Unity. Не сложнее, чем моды для игр.

Но сперва я решил создать какую-то землю, чтобы персонаж смог ходить.

Создание земли (Terrain)

Создать землю в Unity очень легко.

  1. Добавляем на сцену Terrain (GameObject → 3D Objects → Terrain).
  2. Выбираем ее в окне иерархии слева, а справа в Inspector открываем раздел Terrain.
  3. Здесь выбираем режим Paint Terrain (кисточка), ниже в поле выбираем Raise or Lower Terrain и начинаем рисовать себе горы и овраги доступными кистями. Настраиваем размер и просто водим кисточкой по террейну.
  4. Находим в интернете любую бесшовную текстуру травы. Здесь же, в инспекторе выбираем режим Paint Texture, создаем слой и запихиваем туда эту текстуру.

Мои зеленые горы

И вот у нас уже есть какие-то зеленые горы… Сойдет для начала.

Создание персонажа (First Person Controller)

  1. Идем в Asset Store, находим готовый ассет Mini First Person Controller.
  2. Нажимаем кнопку ADD TO MY ASSETS, соглашаемся с лицензией.
  3. Далее нажимаем OPEN IN UNITY.
  4. В самом Unity у нас открывается Package Manager с этим ассетом. Нажимаем Download, ждем загрузки, затем Import, потом еще раз.
  5. Находим в корневой папке проекта новую папку First Person Controller. Заходим внутрь.
  6. Находим там болванку с названием First person controller full и просто перетаскиваем на Terrain. Немного приподнимаем над землей, чтобы не застрял.

Перетаскиваем готового персонажа на сцену

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

Sergey Tenditniy про используемые им методы создания своих выдающихся модульных игровых окружений в Unity.

Перевод статьи с портала 80 level


Добрый день, меня зовут Сергей, я родом из Украины, но последние 3 года живу в красивой стране Словении.

Идея


Желание сделать такой городок появилось у меня в прошлом году, когда мы с моей женой путешествовали по Эльзасу во Франции. Это очень красивая область этой страны, с большим количеством живописных городков и местечек. Я был в восторге от красоты и уникального стиля, которые там меня окружали.

Именно тогда меня посетила идея сделать что-то подобное в виртуальной среде, ведь если люди с удовольствием посещают такие места в реальности, то и виртуальное путешествие будет кому- то интересно.

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


Также мне было интересно попытаться создать что-то в новом для меня стиле, так как ранее я создавал более реалистичные модели. Хотелось понять, что я смогу сделать, работая в стиле мультфильма.


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

Основные фото–референсы, сделанные мной во Франции:


Моделинг

Первые скриншоты по ходу этапов работы:


Основные этапы рабочего процесса:

1. В самом начале, после экспериментального подбора разных вариантов, я создал только первые два домика. Когда получились нужные формы и изгибы, я расположил эти домики на карте с простым освещением – мне хотелось увидеть, как они смотрятся вместе, это нужно было для планирования композиции будущего городка.

2. Далее я добавил имеющимся домикам больше деталей и создал несколько новых.

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

4. Добавлен окончательный набор растительности. Проведена чистовая проработка детализации домиков. Настроено освещение и пост-процессинг. Уже практически готов финальный вид части моего городка. Это послужило мне стилевым и цветовым референсом при проработке остальных улиц и ассетов.

Для этого проекта я решил создать часть домиков на основе одного меша, не используя модульности их структуры. А вторую часть домиков я сделал полностью модульными, чтобы их можно было сложить из отдельных составных частей. Такой подход нужен был из-за того, что я выставил этот городок на продажу, и хотелось чтобы, покупатели имели выбор – пользоваться уже готовыми моделями или собрать их из модульных составных частей (двери, окна, стены и т.п.)


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

На рисунке вы видите, как он выглядит, и из каких элементов собран:


Все элементы:



Поликаунт:



Создаем ассеты

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


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

Растительность


Растительность создавалась очень просто. Ничего нового:

1. Первым делом я создал высокополигональный лист.

2. Использовал запекание нормалей и прозрачность.

3. Затем немного изменил его и с помощью клонирования создал всю ветку.

4. Для создания дерева использовал сферы, затем добавил ранее созданные ветки.

5. Обратите внимание на изображение расположенное ниже – я использовал карту нормалей, полученную на основе сферы для правильного расположения листьев. Часто об этом этапе забывают, но это очень важно.


Для оптимизации всем листьям можно задать одинаковую текстуру. Нужно обеспечить разнообразие цветов использованием vertex color для листьев, сделать их немного более красными или желтыми.

Подобным образом я создал всю растительность, начиная от самой маленькой 3d веточки. Так, можно легко создавать кустарники и даже плющи.


Текстурирование

Я старался обойтись минимумом текстур. Я использовал только текстуры с тайлингом для работы с этим городком (для текстур растительности тайлинг не применялся), при этом не применялось каких либо редких или уникальных изображений.

Были использованы пять основных текстур для создания окружения: бетон, древесина, металл, черепица и листва.

Эти 5 текстур использованы на 95% поверхности того, что вы перед собой видите. Также я использовал текстуры в градациях серого для возможности добавления цвета с использованием vertex color в Unity, всё разнообразие цветов, грязь и потертости древесины я добавил, используя функционал vertex color texture blending с использованием vertex alpha. Я использовал специальный шейдер, созданный в Shader forge, он дал мне возможность смешения с использованием vertex alpha и одновременно overlay vertex color поверх текстур с использованием градаций серого.


На этом изображении вы видите, что я использовал только 4 материала для оформления домиков (древесина, бетон, черепица, стекло), но так, как я использовал vertex color – композиция выглядит интересной и достаточно разнообразной. Один цвет на изображении это один материал в игре.


Все разнообразие цвета создано с использованием vertex color, так каждый из этих домов в сцене может иметь уникальное сочетание цветов, одновременно, это очень не требовательно к ресурсам.

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

Этот стильный вид – результат использования полноцветных и насыщенных текстур, изогнутой геометрии объектов и пост-процессинга.

Инструменты

На первых этапах работы я применил vertex color в Maya, чтобы получить базовые цвета для домиков. А в среде Unity использовал инструмент vertex paint tool для добавления цветов. Из всего разнообразия я выбрал free face paint, при этом можно добавлять цвет сразу на весь полигон и это быстрее чем на каждый вертекс по отдельности. Если у Вас есть шейдер поддерживающий vertex color или смешение текстур, то можно прямо в сцене Unity очень быстро изменить общий вид ваших ассетов.

Вы можете посмотреть, как я это реализовал на этих изображениях:



Освещение

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

Я использовал только real-time направленный свет в этой сцене. Для всего непрямого освещения использовались стандартные средства Unity.

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

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

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

Все остальное сделано с помощью пост-эффектов.

Пост-процессинг

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

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

Заключение

В общем, я могу сказать, что такой подход вполне приемлем для игрового продакшн процесса. Так, как использование текстур с тайлингом и vertex color позволяет реализовывать большие пространства игрового окружения с привлечением относительно небольших ресурсов.

С соответствующей настройкой уровня детализации (LOD) можно получить большое количество элементов детализации переднего плана, а также упростить их для использования на заднем плане. Основные малоразмерные элементы в нашем проекте это растительность, но учитывая то, что при ее создании мы использовали один материал и она состоит из плоских элементов, то правильное использование static batching в Unity сэкономит нам миллионы используемых тут полигонов.

Я не могу точно указать количество часов, потраченное на создание этого окружения, так как занимался им в свободное время после полного рабочего дня. Но, я думаю это более 200 часов в процессе работы от идеи до готового проекта окружения.

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


Первый скрин (объект листка в редакторе)


Второй скрин (листок после падения провалился в террейн)

Это же не логично — коллайдер не должен проваливаться в террейн… Может дело в RigidBody?

Делать листья с коллайдерами) Забавно) В Nature Pack есть падающие листья, могу отсыпать. Они сделаны частицами. Красиво так кружатся.

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

по идеи если есть RigidBody, то он должен упасть прямо на терейн, разверни параметры RigodBody, что бы глянуть что там


По идее да, так и должно быть мне кажется.

В точку, спасибо! Уменьшил Min Penetration For Penalty с 0.01 до 0.001 и всё стало работать как нужно. FPS не изменился.

Частицы можно подвергнуть физике. У меня например был дождь у которого капли отскакивали от коллайдеров. А в новой юнити 4.2 ещё круче вроде они стали. Посмотри свежую демку.

Читайте также: