Разработка
Разработка
Выберите платформу

Игры с открытым миром и потоковая загрузка объектов

Обновлено: 9 дек 2024 г.
Одна из главных задач при разработке крупных игр с открытым миром заключается в поиске способа разместить весь этот мир в памяти во время выполнения. Созданный вами мир содержит множество сеток, материалов, звуковых эффектов, анимаций и событий. Если бы вы попытались загрузить всё сразу, то быстро достигли бы предела памяти системы. Эта проблема встречается даже на самых мощных машинах, но на устройствах с ограниченными аппаратными ресурсами, таких как Meta Quest, она возникает особенно быстро.
К счастью, в любой момент времени игрок взаимодействует только с небольшой частью игрового мира. Например, в высотном здании с сотней этажей и тысячами комнат игрок, скорее всего, будет находиться только на одном или двух этажах. Аналогично, в открытой среде он взаимодействует лишь с ближайшими частями окружения. Аналогично, в открытой среде он взаимодействует лишь с ближайшими частями окружения.
В обоих случаях это означает, что полная детализация игровых объектов требуется только там, где игрок потенциально может с ними взаимодействовать. Наоборот, всё, что находится на достаточном расстоянии, можно загрузить в более низком качестве или вообще не загружать. Это также позволяет выгружать предыдущие области, освобождая память для новых.
Хотя решение этих проблем с памятью может быть не столь простым, мы надеемся, что этот пример проекта поможет вам выбрать правильное направление. В нем мы опишем процесс оценки объектов (на примере проекта Dead & Buried 2 от Oculus Studios), профилирование производительности игры во время выполнения, создание объектов с разными уровнями детализации (LOD), систему для загрузки и выгрузки этих LOD в зависимости от положения игрока, а также инструменты для проверки того, как работают системы управления объектами в игре. В заключение мы предложим идеи для дальнейшего улучшения производительности приложения.
Следует отметить, что этот пример фокусируется на загрузке геометрии уровней, однако реальный проект также должен учитывать игровые объекты и их многочисленные компоненты, такие как аудио, анимация, дополнительные сетки и многое другое. Работая с этим, помните, что основные принципы этой статьи всё равно применимы. А именно: подготовьте объекты с разными уровнями детализации, не загружайте больше, чем необходимо, и избегайте выполнения сложных операций в одном кадре.

Оценка сложности объектов

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

Сложность сеток

Mesh Complexity
Чтобы проверить сложность сеток в сцене, самый простой способ — включить режим отображения каркаса в редакторе. В этой сцене сложность сеток выглядит неплохо. Большинство треугольников занимают много пикселей, но на лестнице и в церкви мы видим много очень тонких треугольников. Невозможно разобрать мелкие детали на объектах, расположенных на большом расстоянии, поэтому они, скорее всего, должны иметь более низкий уровень детализации.
Обязательно включите окно статистики, чтобы видеть количество треугольников в текущем виде. В этом случае видимость составляет 525 000 треугольников. Это много. Однако учтите, что в редакторе также отображаются только служебные примитивы, неиспользуемые элементы интерфейса и не используется система отсечения невидимых объектов. В игре количество треугольников должно быть значительно ниже.
Хотя в документации, посвященной количеству треугольников в Meta Quest, указаны целевые значения для каждой гарнитуры, на больших уровнях с открытым миром следует ориентироваться на 50 % от целевого количества треугольников. Это связано с тем, что целевые значения получены в результате изучения рабочих нагрузок на более мелких вложенных уровнях. На небольших вложенных уровнях геометрия может быть сложнее из-за меньшего расстояния отрисовки и более эффективной работы системы отсечения невидимых объектов. После отсечения большая часть треугольников перестанет отрисовываться, даже если технически они находятся в пределах пирамиды видимости. На больших открытых уровнях необходимо регулировать сложность геометрии, так как в этом случае отсечение будет менее эффективным. Однако помните, что эти показатели во многом зависят от сложности шейдеров.
Наконец, важно уменьшить количество мелких треугольников на экране. Это не только снижает количество вершин, но и уменьшает число дополнительных выборок, которые отрисовываются при включении MSAA.

Сложность шейдеров

Shader code
Чтобы проверить сложность шейдера, сначала изучите его код. В этом случае сразу видно, что шейдеры довольно простые. Прямое освещение отсутствует. Все освещение поступает из карты освещения, поэтому вычисление цвета фрагмента сводится к нескольким выборкам текстур и некоторым скалярным умножениям.
Shader disassembly and statistics
Для более точной оценки сложности шейдера можно выбрать шейдер в браузере проекта, а затем нажать Compile and show code (Компилировать и показать код) для D3D. Таким образом будет выведен дизассемблированный код шейдера и основные статистические данные. Хотя дизассемблирование сложнее для восприятия человеком, оно позволяет легко определить, какие шейдеры более ресурсоемки, поскольку более сложные шейдеры требуют большего числа операций. Как правило, более высокие показатели статистики и больше строк в коде дизассемблирования означают, что шейдер будет работать медленнее.

Измерение и снижение количества вызовов отрисовки

При оптимизации производительности ГП одна из главных задач заключается в уменьшении количества вызовов отрисовки за кадр. Подробнее см. в следующих статьях:
После изучения этих материалов можно продолжить.
View of a level
На изображении представлена одна из частей уровня, вид с самой южной точки в северном направлении. Unity объединяет вызовы отрисовки, использующие одинаковые материалы и свойства, в так называемые статические пакеты. Однако в этом случае между почти каждым вызовом отрисовки был вызов SetPass, который разрушал статические пакеты. Это было вызвано настройками создания карт освещения. Уровень имел двенадцать карт освещения, и объекты, находящиеся рядом, часто использовали разные карты.
Draw calls with original lightmaps
Мы смогли сократить количество вызовов установки прохода, принудительно назначив эти сетки одной и той же карте освещения. Это было сделано с помощью объектов параметров карты освещения. Они позволяют группировать объекты с помощью параметра System Tag (Системная метка). Также можно ограничить количество карт освещения, которые разрешено создавать для объектов с определенным параметром. Мы назначили рельеф в карте освещения 0, а остальные сетки — в карте освещения 1. Для наших целей это позволило получить качественные карты освещения. Это также дало возможность снизить количество вызовов SetPass с 148 до 16. Общее количество пакетов уменьшено со 156 до 94.
Draw calls with optimized lightmaps
Вы также можете уменьшить количество карт освещения, уменьшив параметр Lightmap Resolution (Разрешение карты освещения) в настройках создания карт освещения. Это приведет к схожему эффекту, но у вас будет меньше контроля над результатом.

Объединение сеток и генерация уровней детализации (LOD)

Примечание. Под объединением сеток понимается процесс объединения нескольких сеток в одну крупную. Под статическим объединением подразумевается объединение вызовов отрисовки в Unity. Статический пакет имеет единый вызов установки прохода, но может включать несколько вызовов отрисовки. Объединенная сетка использует только один вызов отрисовки.
Наш уровень был построен с использованием смеси сложных и модульных сеток. Для зданий часто применялся сложная сетка, которая затем украшалась меньшими модульными элементами. Некоторые части уровня были полностью созданы из модульных элементов. Без объединения сеток это привело бы к тысячам вызовов отрисовки и установки прохода. Кроме того, у этих сеток отсутствовали уровни LOD, поэтому их полная детализация использовалась бы, даже если бы объект был едва виден на экране.
Scene view showing a mix of large building meshes, medium logs and tent, and small ice details.
На этом скриншоте видно, что здание и камень представляют собой крупные сетки. Деревянные бревна и палатка относятся к средней категории, а лед на двери — к небольшим объектам.
Шейдеры здесь также не используют прямого освещения. Весь свет исходит из карт освещения. Следует отметить, что карты освещения могут нарушить статическое объединение, поэтому важно, чтобы сетки использовали одну и ту же карту освещения по возможности. Также важно, чтобы все уровни LOD одной сетки имели общую карту освещения. Это уменьшает использование памяти (меньше карт освещения), устраняет несоответствия в освещении при переключении уровней LOD и позволяет запекать карту освещения всего один раз вместо повторения для каждого LOD. Если ваши объекты имеют уровни LOD, убедитесь, что их UV-развертки для карты освещения совпадают с сеткой LOD0 (самого детализированного уровня).

Цели

Unity profiler rendering window
Это скриншот окна рендеринга профилировщика Unity на начальном этапе. На этом этапе у нас было 4 105 вызовов отрисовки, 129 вызовов установки прохода, 342 статических пакета, а также обрабатывалось 1,4 миллиона треугольников.
Из опыта мы знаем, что для оригинального Quest, используя API GLES, необходимо ориентироваться на менее чем 100 вызовов отрисовки для статической геометрии уровня и около 200 вызовов отрисовки в целом, в том числе динамических. При разработке для Quest 2 или использовании API Vulkan эти целевые значения могут быть немного увеличены.
Целевой предел количества треугольников зависит от сложности шейдеров. В нашем случае у нас используется очень простой фрагментный шейдер, что оставляет нам значительный запас производительности на ГП. Это позволяет рендерить большое количество треугольников. Из опыта мы знаем, что для оригинального Quest с использованием фрагментного шейдера, содержащего карты освещения, прямое освещение от одного направленного источника света и карты нормалей и зеркального отражения, предел составляет около 300 тысяч треугольников. В нашем случае, где используется только создание карт освещения, мы можем без проблем обработать более миллиона треугольников.
Особое внимание следует уделять вызовам установки прохода, Они могут быть очень затратными для потока рендеринга, особенно при использовании GLES. Вызов установки прохода происходит каждый раз, когда между вызовами отрисовки переключается материал. Наша цель — иметь только один вызов установки прохода для всей статической геометрии уровня, исключая такие элементы, как небесный купол (skybox) и ландшафт, так как они требуют значительно отличающихся шейдеров по сравнению с обычной геометрией. Для этого вам нужно будет объединить текстуры в атлас текстур или массив текстур, а затем изменить сетки и шейдеры так, чтобы они обращались к правильным текстурным координатам.
Наш мир состоит из четырех подуровней, которые затем повторяются, создавая один большой мир. Мы хотели иметь возможность запекать каждый подуровень отдельно. Это позволяет каждому подуровню использовать разные настройки уровней детализации (LOD). Кроме того, если один из подуровней изменится, нам нужно будет запекать только его, а не все четыре.

Наш подход

Для каждого подуровня мы выполняем следующие шаги: запекание материалов, генерация сетки для карты освещения, запекание карт освещения, создание LOD и структуры LOD.

Запекание материалов

На этом этапе мы создаем один материал, который будет использоваться на всех сетках LOD. Мы смогли использовать текстурные атласы для объединения текстур, используемых исходными материалами. У нас было немного уникальных текстур на уровень, поэтому все текстуры удалось разместить в одном текстурном атласе размером 4096x4096 без потери качества.
Если текстурного атласа 4096x4096 недостаточно для хранения всех текстур, стоит рассмотреть вариант объединения текстур в текстурный массив. После создания новых текстур необходимо изменить текстурные координаты сеток, чтобы они использовали правильные UV-координаты текстур.

Генерация сетки для карты освещения

Мы объединили все сетки в одну большую сетку. Затем мы использовали Unity для развертки этой сетки и генерации UV-координат для карты освещения. Эта сетка будет использоваться для создания карт освещения, а UV-координаты световой карты будут скопированы на наши сетки LOD.

Запекание карт освещения

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

Создание LOD

Наша система объединяет сетки, группируя их в ячейки. Каждая ячейка создает объединенную сетку из всех сеток, которые она содержит. Каждый уровень LOD удваивает размер ячеек по сравнению с предыдущим уровнем. Это позволяет нам позже использовать иерархическую систему LOD, основанную на дереве квадрантов. Чтобы уменьшить сложность для удаленных сеток, на более высоких уровнях LOD удаляются мелкие объекты перед объединением сеток. Затем мы объединяем сетки, чтобы создать LOD для этой ячейки. Для дальнейшего уменьшения количества треугольников можно удалить все треугольники, расположенные ниже поверхности местности.
После генерации мы копируем UV-координаты карты освещения с сетки световой карты на сетку LOD. На этом этапе все вершины имеют идентичные координаты в сетке карты освещения, поскольку мы просто удалили вершины из исходной сетки.
Затем мы упрощаем объединенную сетку для всех уровней, кроме LOD0, на каждом уровне допуская всё большую ошибку. Этот процесс выполняется после копирования UV-координат карты освещения, так как он может изменить положение вершин.
LOD meshes
Белые линии обозначают ячейки. Зеленые квадраты представляют сетки LOD0. Синие квадраты представляют сетки LOD1. Красный квадрат представляет сетку LOD2. Каждый квадрат — это отдельная сетка. В LOD0 видно множество мелких объектов (костер, бревна). В LOD1 мелкие объекты удалены, но деревья и здания остаются видимыми. В LOD2 остаются только крупные объекты (здания, скалы). Обратите внимание: световая карта для LOD2 по-прежнему содержит тени от удаленных сеток. Это происходит потому, что карты освещения используются совместно между всеми уровнями LOD.

Создание структуры LOD

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

Сторонние библиотеки

В нашей реализации генератора LOD мы использовали пакет из Asset Store под названием Mesh Baker. Мы настоятельно рекомендуем использовать Mesh Baker для создания текстурных атласов и объединения сеток. В качестве альтернативы вы можете написать свой собственный инструмент или получить лицензию на такие решения, как Simplygon.

Объединение подуровней

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

Карты освещения

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

Сцены

Для возможности потоковой загрузки каждая сетка LOD должна быть помещена в свою собственную сцену. Каждая сцена должна содержать только одну сетку. Сетка уже смещена в своё финальное положение. Сами сцены не имеют трансформации, поэтому если сетки не сместить заранее, они все будут отображаться в начале координат уровня.
Каждая сцена должна быть добавлена в настройки сборки. Затем системе LOD необходимо предоставить индекс сцены, которая содержит соответствующую сетку LOD, из настроек сборки.

Система уровней детализации (LOD)

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

Два подхода

Изначально мы использовали систему, основанную на сетке, где каждая ячейка вычисляла свое расстояние до камеры, начиная с самого грубого уровня детализации (LOD2) и до самого детализированного (LOD0). Уровень LOD ячейки мог быть изменен только в том случае, если все сетки в области, покрываемой более высоким уровнем LOD, заменялись сетками более низкого уровня, чтобы избежать появления пробелов. Однако эта система оказалась медленной и плохо масштабировалась для больших уровней с большим количеством ячеек. Этот подход может быть полезен, если вам не нужно, чтобы каждый уровень LOD был ровно в два раза больше предыдущего. Например, можно сделать так, чтобы ячейки LOD0 и LOD1 имели одинаковый размер, но LOD1 использовал более упрощенную сетку.
Мы переключились на иерархический подход, основанный на структуре дерева квадрантов. Этот метод позволяет эффективно покрывать большие области. Если мы определяем, что ячейка должна отображать LOD2, то проверки для ячеек LOD1 и LOD0 можно пропустить, так как они входят в область LOD2. Это означает, что большинство ячеек вообще не проверяются. Иерархическая структура также упрощает потоковую загрузку. Можно легко реализовать ленивую загрузку, отображая родительский LOD до завершения загрузки всех дочерних LOD. Либо можно всегда загружать дочерние ячейки текущей, что увеличивает использование памяти.
Quadtree LOD grid with green LOD0 cells near camera, blue LOD1, and red LOD2 at distance.
Если камера находится в желтой ячейке, создается следующая структура уровней детализации (LOD): зелёный цвет обозначает LOD0, синий — LOD1, красный — LOD2. В этом случае загружено 20 сеток LOD2, но видимы только 14 из них. Загружено 24 сетки LOD1, из которых видимы 20. Все 16 сеток LOD0 загружены и видимы. Белые ячейки на внешнем краю представляют собой узлы из структуры октодерева; они не содержат сеток.

Состояние LOD

Каждый раз, когда камера перемещается в другую ячейку, мы проходим дерево от макушки к корню. На каждом уровне дерева мы рассчитываем расстояние от камеры в ячейках. Размер ячейки на каждом уровне в два раза меньше, чем на предыдущем. Если расстояние от камеры меньше одной ячейки, мы загружаем, но не отображаем сетку и переходим на следующий уровень. Если спускаться дальше не нужно или если достигнут лист дерева, мы загружаем и отображаем сетку, связанную с этим узлом. Узлы, которые загружаются, уведомляют родительский узел.
Сначала выполняется проход для расчета желаемого состояния каждого узла. Затем выполняется второй проход, чтобы применить правильное состояние. Это делается для того, чтобы дочерние узлы не становились видимыми, пока все они не будут загружены. В противном случае можно увидеть, как сетки появляются резко. Если узел знает, что любой из его дочерних узлов загружается, он делает себя видимым, сохраняя дочерние узлы невидимыми. Каждый раз, когда дочерний узел заканчивает загрузку, он уведомляет об этом родительский узел. Родительский узел включает дочерние узлы и отключает себя, когда все они закончат загрузку.
Чтобы избежать частых изменений состояния LOD, когда камера находится на границе между ячейками, система LOD не обновляется, пока камера не переместится как минимум на 1 метр от предыдущей позиции, где происходило последнее вычисление состояний LOD.
Подробнее см. в следующих файлах:

Асинхронная загрузка и выгрузка

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

Выгрузка объектов

При добавочной загрузке сцены все используемые в ней объекты также загружаются. Однако при выгрузке сцены эти объекты не выгружаются. Из-за этого использование памяти продолжает расти, пока все уровни LOD находятся в памяти.
Когда Quest начинает испытывать нехватку памяти, вы можете заметить рывки в работе, которые в конечном итоге могут привести к зависаниям и сбоям. Это происходит потому, что в фоновом режиме системой вызывается демон очистки памяти.
Unity предоставляет два способа высвобождения этих теперь уже неиспользуемых объектов:
  • Resources.UnloadUnusedAssets — очень медленная операция, которая выгружает все неиспользуемые объекты. Ее нельзя эффективно использовать во время игрового процесса. Она приведет к пропущенным кадрам. Однако ее можно применять, когда игрок телепортируется или находится на экране загрузки.
  • Resources.UnloadAsset — может использоваться для выгрузки объектов во время игрового процесса. Вам всё равно потребуется система распределения нагрузки, поскольку это ресурсоемкая операция (выгрузка одной сетки может занимать около 0,3 мс в основном потоке).
Примечание. Resources.UnloadAsset работает только в том случае, если вы передаете сетку, используемую сценой, которую вы выгружаете. Если вы попытаетесь передать копию той же сетки, будет выгружена только копия. Мы пробовали сериализовать все сетки, используемые узлом LOD, чтобы избежать их сбора во время выполнения, но это не сработало, так как сериализованные скриптом сетки представляют собой копии, а не оригинальные сетки, используемые сценой.

Инструменты отладки

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

Режим отладки LOD

Для наглядной визуализации отображаемых уровней LOD мы написали шейдер, который выводит цвет на основе целочисленного параметра материала (соответствующего индексу уровня LOD: зелёный для самого детализированного уровня и красный для наименее детализированного). Во время выполнения мы создаем несколько материалов с использованием этого шейдера: по одному для каждого уровня LOD и ещё один, указывающий на переход между уровнями LOD. При загрузке сцена проверяет, включена ли отладка LOD. Если да, то применяется материал, соответствующий текущему уровню LOD.
Материал для отладочного представления не имеет специфичных для материала свойств. Это позволяет использовать один и тот же материал для всех сеток одного уровня LOD. Однако свойства сеток остаются доступными, и поскольку наши данные карты освещения относятся к глобальным параметром, мы смогли использовать их для создания глубины в визуализации, которая иначе выглядела бы плоской из-за особенностей отладочного шейдера.

Режим тестирования производительности

Полезно иметь возможность запускать игру в предсказуемом, заранее заданном автоматическом режиме. Такой режим можно, например, запускать автоматически каждую ночь, чтобы убедиться, что после дня коммитов всё продолжает работать, как ожидается.
Для этого режима мы сначала создали простой инструмент для создания пути из набора контрольных точек. Камера следует по этому пути с постоянной скоростью. Направление камеры также фиксируется, чтобы исключить изменения в результатах тестирования из-за направления обзора.
В нашем первом подходе использовались кривые Безье для создания плавного пути. Хотя этот вариант был рабочим, он усложнял процесс создания пути, поэтому мы переключились на использование прямых линий между контрольными точками.
Наш инструмент автоматически размещал контрольные точки точно на два метра выше уровня земли. Это важно, потому что слишком низкое или слишком высокое положение камеры может повлиять на производительность отсечения невидимых объектов.
Мы не фиксируем положение камеры строго на пути. Вместо этого камера всегда смотрит на одну метр вперед по пути, затем перемещается вперед, стараясь сохранить расстояние в один метр. Это позволяет немного сгладить резкие углы. При установке направления обзора камеры мы изменяем только вращение вокруг оси Y. Это ограничение вызвано тем, что изменение вращения по осям X или Z может привести к крену камеры в OVR Camera Rig, из-за чего камера может перевернуться или каждый кадр менять направление вперед.

Камера свободного полета

Простой способ убедиться, что система LOD работает правильно, — это увидеть все плитки в кадре одновременно. Единственный способ сделать это — использовать камеру в режиме свободного полета.
Для этого режима мы модифицировали скрипт SimpleCapsuleWithStickMovement из Unity Starter Samples, добавив возможность перемещения по оси Y. Также мы отключили гравитацию для компонента rigidbody. Как и в режиме тестирования производительности, не пытайтесь вращать OVR Camera Rig вокруг чего-либо, кроме оси Y.

Принудительная установка уровня LOD

Система LOD настроена на увеличение детализации при приближении камеры, но во время разработки иногда требуется рассмотреть низкодетализированные модели вблизи. Чтобы упростить это, мы добавили режим, позволяющий зафиксировать LOD на определенном уровне.
При принудительной установке уровня LOD необходимо учитывать, что установка всего на LOD0 приведёт к увеличению объема используемой памяти. Здесь мы не ограничены памятью, поэтому можем одновременно загружать все модели уровня LOD0. Однако в более сложных средах это может быть невозможным. Если вы столкнулись с такой ситуацией, рассмотрите вариант принудительной установки уровня LOD только для ячеек, которые обычно видимы (как сделано здесь). Если ячейка находится настолько далеко, что даже LOD2 (наш самый высокий уровень LOD) не отображается, то для этой ячейки не нужно принудительно задавать уровень LOD. Это легко реализуется с помощью стандартного обхода дерева до узлов уровня LOD2, после чего можно принудительно продолжить обход вниз по дереву до достижения нужного уровня LOD.

Фиксация уровня LOD

Чтобы зафиксировать уровни LOD для моделей, достаточно остановить обновление системы LOD.
Подробнее см. в следующих файлах:

Использование системы LOD

Систему уровней детализации (LOD) можно использовать как есть, без каких-либо модификаций или улучшений. К сожалению, использовать генератор LOD нельзя, потому что нам пришлось удалить часть кода. Для использования системы LOD вам придется сгенерировать собственные сетки LOD. Подробнее см. в разделе Сторонние библиотеки выше.
Уровни LOD должны соответствовать сеточной структуре, причем более высокие уровни должны быть в два раза больше предыдущих. Это необходимо, поскольку система LOD использует внутреннее дерево квадрантов. Чтобы заполнить LOD Manager, вызовите метод LODManager.SetLOD. Этот метод создаст дерево квадрантов из переданных объектов. На этом этапе потоковая загрузка LOD пока не будет использоваться, и все модели будут загружены (система просто включает или выключает их).
Чтобы включить потоковую загрузку моделей, добавьте в вашу сцену скрипт Sublevel Combiner и выполните его. Это создаст отдельную сцену для каждой модели и настроит LOD Manager для использования потоковой загрузки. Эти системы предполагают определенные настройки материалов, карт освещения и столкновений для ваших уровней LOD. Возможно, их потребуется модифицировать, чтобы они соответствовали вашей конфигурации.

Отсечение невидимых объектов

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

Встроенная система отсечения

Встроенная в Unity система отсечения объектов проста в использовании, данные отсечения генерируются быстро и обладают высокой точностью. Однако она не всегда подходит для мобильных устройств. Эта система, как правило, потребляет много памяти независимо от размера данных отсечения. Она также сильно нагружает ЦП, занимая значительное время основного потока.
Несмотря на эти недостатки, мы использовали встроенную систему отсечения, так как у нас не было ограничений по памяти или процессору. Чтобы сгенерировать данные отсечения для сцены, сначала загрузите все модели LOD (аддитивно загрузите сцены, содержащие эти модели), а затем создайте данные отсечения, как обычно.

Пользовательская система отсечения

В более сложной игре встроенная система отсечения может быть неприменима по вышеуказанным причинам. В таких случаях можно создать собственную систему отсечения объектов, которая будет лучше подходить для мобильных устройств.
Для статической геометрии такую систему можно реализовать относительно просто. Необходимо разделить сцену на ячейки. Для каждой ячейки выполняется рендеринг сцены в кубическую карту с использованием пользовательского материала, который выводит уникальный идентификатор для каждого объекта. Затем данные кубической карты считываются на ЦП, при этом собираются идентификаторы всех видимых сеток. Эти результаты сохраняются и используются во время работы приложения. На этапе выполнения сетки, не входящие в список видимых, отключаются при входе в ячейку. Проверки видимости не выполняются в реальном времени, что делает этот подход очень дружелюбным к ЦП.
Это упрощенное объяснение. Наивная реализация может привести к значительно худшей производительности по сравнению с использованием встроенной системы отсечения. Кроме того, время генерации данных отсечения может стать проблемой в зависимости от размера уровней и метода реализации.
Логотип навигации
Русский
© 2026 Meta