Это третья статья из серии, в которой рассказывается о советах по оптимизации для ваших проектов Unity. Используйте их как руководство для работы с более высокой частотой кадров при меньших ресурсах. Попробовав эти лучшие практики, обязательно ознакомьтесь с другими страницами этой серии:
- Настройка проекта Unity для повышения производительности
- Оптимизация производительности для высококлассной графики
- Продвинутое программирование и архитектура кода
- Улучшенная физическая производительность для плавного игрового процесса
Узнайте об ограничениях целевого оборудования и о том, как профилировать GPU для оптимизации рендеринга графики. Попробуйте воспользоваться этими советами, чтобы снизить нагрузку на GPU.
Еще больше лучших практик вы найдете в бесплатной электронной книге, Оптимизируйте производительность игр для консолей и ПК.
Чтобы нарисовать игровой объект на экране, Unity выполняет вызов графического API (например, OpenGL, Vulkan или Direct3D). Каждый вызов рисунка требует больших затрат ресурсов.
Изменения состояния между вызовами рисования, такие как переключение материалов, могут вызвать перегрузку производительности на стороне процессора. Аппаратные средства ПК и консолей могут выполнять много вызовов рисования, но накладные расходы на каждый вызов все равно достаточно велики, чтобы попытаться их уменьшить. На мобильных устройствах оптимизация звонков при розыгрыше крайне важна. Этого можно добиться с помощью пакетной обработки вызовов рисования.
Пакетная обработка вызовов рисования минимизирует эти изменения состояния и снижает затраты процессора на рендеринг объектов. Unity может объединять множество объектов в меньшие пакеты, используя несколько техник с помощью конвейера рендеринга высокой четкости (HDRP) или универсального конвейера рендеринга (URP):
- Дозирование SRP: Включите SRP Batcher в Pipeline Asset в разделе Advanced. При использовании совместимых шейдеров SRP Batcher сокращает время настройки GPU между вызовами рисования и делает данные о материалах постоянными в памяти GPU. Это также может значительно ускорить время рендеринга на процессоре. Используйте меньше вариантов шейдеров с минимальным количеством ключевых слов, чтобы улучшить пакетную обработку SRP. Обратитесь к документации SRP, чтобы узнать, как ваш проект может воспользоваться преимуществами этого рабочего процесса рендеринга.
- Инстансирование GPU: Если у вас есть большое количество одинаковых объектов с одинаковой сеткой и материалом, используйте GPU instancing для пакетной передачи их через графическое оборудование. Чтобы включить инстансирование GPU, выберите материал в окне проекта в Инспекторе, а затем установите флажок Enable instancing.
- Статическое дозирование: Для неподвижной геометрии Unity может уменьшить количество вызовов рисования для сеток с одним и тем же материалом. Это более эффективно, чем динамическое пакетирование, но использует больше памяти. Пометьте все сетки, которые никогда не двигаются, как Batching Static в Инспекторе. Во время сборки Unity объединяет все статические сетки в одну большую сетку. Класс StaticBatchingUtility также позволяет создавать эти статические партии во время выполнения (например, после генерации процедурного уровня недвижущихся частей).
- Динамическое дозирование: Для небольших сеток Unity может группировать и трансформировать вершины на процессоре, а затем отрисовывать их за один раз. Однако не стоит использовать этот способ, если у вас нет достаточного количества низкополигональных сеток (не более 300 вершин в каждой и 900 общих вершинных атрибутов). В противном случае его включение приведет к трате процессорного времени на поиск мелких сеток для пакетной обработки.
Вы можете максимально использовать пакетную обработку несколькими простыми способами:
- Используйте как можно меньше текстур в сцене. Меньшее количество текстур требует меньшего количества уникальных материалов, что упрощает их производство. Кроме того, по возможности используйте атласы текстур.
- Всегда запекайте лайтмапы с максимально возможным размером атласа. Меньшее количество лайтмапов требует меньшего количества изменений состояния материала, но следите за занимаемой памятью.
- Будьте осторожны, чтобы случайно не инкрустировать материалы. Доступ к Renderer.material в скриптах дублирует материал и возвращает ссылку на новую копию. Это разрушает все существующие партии, в которые уже включен материал. Если вы хотите получить доступ к материалу пакетного объекта, используйтеRenderer.sharedMaterial вместо этого.
- Следите за количеством статических и динамических пакетов в сравнении с общим количеством вызовов рисования, используя профилировщик или статистику рендеринга во время оптимизации.
Дополнительные сведения см. в документации по пакетной обработке вызовов Draw.
Используйте отладчик кадров, чтобы остановить воспроизведение на одном кадре и проследить за тем, как Unity строит сцену. При этом вы сможете выявить возможности для оптимизации. Найдите игровые объекты, которые рендерятся без необходимости, и отключите их, чтобы уменьшить количество вызовов рисования на кадр.
Одно из главных преимуществ отладчика кадров заключается в том, что вы можете связать вызов рисования с конкретным игровым объектом в сцене. Это облегчает исследование некоторых проблем, которые могут быть невозможны во внешних фреймовых отладчиках.
Примечание: Отладчик кадров не показывает отдельные вызовы рисования или изменения состояния. Хотя только встроенные профилировщики GPU могут предоставить вам подробную информацию о вызовах отрисовки и синхронизации, отладчик кадров все же может быть очень полезен для отладки проблем с конвейером или пакетной обработкой.
Более подробную информацию можно найти в документации по отладчику Frame Debugger.
Скорость заполнения - это количество пикселей, которые графический процессор может выводить на экран каждую секунду. Если ваша игра ограничена коэффициентом заполнения, это означает, что она пытается отрисовать больше пикселей за кадр, чем может обработать графический процессор.
Рисование поверх одного и того же пикселя несколько раз называется перерисовкой. Перерисовка снижает скорость заполнения и требует дополнительной пропускной способности памяти. Наиболее распространенными причинами перерасхода средств являются:
- Перекрытие непрозрачной или прозрачной геометрии
- Сложные шейдеры, часто с несколькими проходами рендеринга
- Неоптимизированные частицы
- Перекрывающиеся элементы пользовательского интерфейса
Хотя вы должны минимизировать его влияние, универсального подхода к решению проблемы овердрафта не существует. Экспериментируйте со следующими приемами, чтобы уменьшить его влияние.
Как и в случае с другими платформами, оптимизация на консолях часто означает уменьшение количества тиражей. Вот несколько приемов, которые могут помочь:
- Используйте Выделение окклюзии чтобы удалить объекты, скрытые за объектами переднего плана, и уменьшить перерисовку. Имейте в виду, что это требует дополнительной обработки CPU, поэтому используйте профилировщик, чтобы убедиться, что перенос работы с GPU на CPU действительно полезен.
- Инстансирование на GPU также может уменьшить количество операций, если у вас много объектов с одинаковой сеткой и материалом. Ограничение количества моделей в сцене может повысить производительность. Если это сделано искусно, вы можете построить сложную сцену, не делая ее повторяющейся.
- SRP Batcher может сократить время настройки GPU между вызовами рисования за счет пакетной передачи команд Bind и Draw GPU. Чтобы воспользоваться преимуществами пакетной обработки SRP, используйте столько материалов, сколько нужно, но ограничьте их небольшим количеством совместимых шейдеров (например, шейдеры Lit и Unlit в URP и HDRP).
Выбраковка происходит для каждой камеры и может сильно повлиять на производительность, особенно при одновременном включении нескольких камер. Unity использует два типа отбраковки:
- Выборочная обработка выполняется автоматически на каждой камере. Это гарантирует, что объекты GameObject, находящиеся за пределами фрустум вида не рендерились, чтобы сэкономить на производительности.
- Вы можете задать расстояния обрезки для каждого слоя вручную с помощью Camera.layerCullDistances. Это позволяет отсеивать маленькие игровые объекты на расстоянии меньшем, чем стандартноеfarClipPlane свойство. Для этого организуйте объекты GameObjects в слои. Используйте массив .layerCullDistances, чтобы присвоить каждому из 32 слоев значение, меньшее, чем farClipPlane (или используйте 0, чтобы установить значение по умолчанию для farClipPlane).
- Единство сначала очищает по слоям. Он сохраняет GameObjects только на тех слоях, которые использует камера. После этого с помощью функции Frustum culling удаляются все игровые объекты, находящиеся за пределами Camera Frustum.
- Очистка от мусора выполняется в виде серии заданий, в которых задействованы доступные рабочие потоки. Каждый тест на очистку слоя выполняется быстро (по сути, это просто операция с битовой маской). Однако при большом количестве игровых объектов эти расходы могут увеличиться. Если это станет проблемой для вашего проекта, вам, возможно, придется внедрить какую-то систему для разделения мира на "сектора" и отключения секторов, находящихся за пределами Camera Frustum, чтобы немного снизить нагрузку на систему отсеивания слоев/фрагментов Unity.
- Окклюзия удаляет любые игровые объекты из игрового вида, если камера их не видит. Объекты, скрытые за другими объектами, потенциально могут продолжать рендериться и тратить ресурсы. Чтобы избавиться от них, воспользуйтесь окклюзионной очисткой.
- Например, рендеринг комнаты не нужен, если дверь закрыта и камера не видит комнату. Если включить функцию удаления окклюзий, это может значительно повысить производительность, но при этом увеличится объем дискового пространства, процессорного времени и оперативной памяти. Unity запекает данные окклюзии во время сборки, а затем должна загрузить их с диска в оперативную память при загрузке сцены.
- В то время как удаление фрустра за пределами вида камеры происходит автоматически, удаление окклюзии - это запекаемый процесс. Просто отметьте объекты как статичные, окклюдеры или окклюдеры, а затем запеките их в диалоге Window > Rendering > Occlusion culling.
Дополнительные сведения см. в учебном пособии "Работа с окклюзией ".
Настройка Allow Dynamic Resolution Camera позволяет динамически масштабировать отдельные цели рендеринга, чтобы снизить нагрузку на GPU. В случаях, когда частота кадров в приложении снижается, можно постепенно уменьшать разрешение, чтобы сохранить постоянную частоту кадров.
Unity запускает это масштабирование, если данные о производительности указывают на то, что частота кадров скоро снизится из-за привязки к GPU. Вы также можете упреждающе запустить это масштабирование вручную с помощью скрипта. Это полезно, если вы приближаетесь к разделу приложения, требовательному к GPU. При постепенном масштабировании динамическое разрешение может быть практически незаметным.
Список поддерживаемых платформ см. на странице руководства по динамическому разрешению.
Иногда во время игры вам нужно сделать рендер с нескольких точек зрения. Например, в шутерах от первого лица (FPS) принято рисовать оружие игрока и окружение отдельно, с разными полями зрения (FOV). Благодаря этому объекты переднего плана не выглядят искаженными через широкоугольный FOV заднего плана.
Вы можете использовать Сложение камер в URP для визуализации более чем одного вида камеры. Тем не менее, для каждой камеры все равно приходится выполнять значительную сортировку и рендеринг. Каждая камера несет определенные накладные расходы, независимо от того, выполняет она значимую работу или нет.
Используйте только компоненты камеры, необходимые для рендеринга. На мобильных платформах каждая активная камера может использовать до 1 мс процессорного времени даже при отсутствии рендеринга.
По мере удаления объектов уровень детализации (LOD) может регулироваться или переключаться, чтобы использовать сетки более низкого разрешения с более простыми материалами и шейдерами. Это повышает производительность GPU.
Более подробную информацию можно найти в курсе "Работа с LODами " на Unity Learn.
Профилируйте эффекты постобработки, чтобы узнать их стоимость для GPU. Некоторые полноэкранные эффекты, такие как Bloom и Depth of Field, могут быть дорогими, поэтому стоит поэкспериментировать, пока вы не найдете необходимый баланс между качеством изображения и производительностью.
Постобработка не сильно колеблется во время выполнения. После того как вы определились с параметрами Volume Overrides, выделите эффектам постобработки статичную часть общего бюджета кадра.
Тесселяция разделяет фигуру на более мелкие версии, что позволяет повысить детализацию за счет увеличения геометрии. Хотя есть примеры, когда тесселяция имеет наибольший смысл, например, кора дерева в демо-версии Unity Book of the Dead, старайтесь избегать тесселяции на консолях, так как они дорого обходятся графическому процессору.
Подробнее о демоверсии "Книги мертвых " читайте здесь.
Как и шейдеры тесселяции, геометрические и вершинные шейдеры могут работать на GPU дважды за кадр - один раз во время предварительной обработки глубины, а второй - во время обработки тени.
Если вы хотите генерировать или изменять вершинные данные на GPU, вычислительный шейдер часто является лучшим выбором, особенно по сравнению с геометрическим шейдером. Выполнение работы в вычислительном шейдере означает, что вершинный шейдер, который собственно и отрисовывает геометрию, может работать гораздо быстрее.
Узнайте больше о базовых концепциях шейдеров.
Когда вы отправляете вызов рисования на GPU, эта работа разбивается на множество волновых фронтов, которые Unity распределяет между доступными SIMD в GPU. Каждый SIMD имеет максимальное количество волновых фронтов, которые могут работать одновременно.
Заполненность волнового фронта означает, сколько волновых фронтов используется в данный момент по отношению к максимальному значению. Он измеряет, насколько эффективно вы используете потенциал графического процессора. Инструменты профилирования для консольной разработки очень подробно показывают занятость волнового фронта.
В приведенном выше примере из Unity's Book of the Dead волновые фронты вершинного шейдера отображаются зеленым цветом, а волновые фронты пиксельного шейдера - синим. На нижнем графике появляется множество волновых фронтов вершинного шейдера без особой активности пиксельного шейдера. Это свидетельствует о недостаточном использовании графического процессора.
Если вы выполняете много работы с вершинными шейдерами, которая не приводит к появлению пикселей, это может указывать на неэффективность. Хотя низкая заполняемость волнового фронта - это не обязательно плохо, это метрика, которую можно использовать для оптимизации шейдеров и проверки других узких мест. Например, если вы задерживаетесь из-за операций с памятью или вычислениями, увеличение заполненности может повысить производительность. С другой стороны, слишком большое количество летящих волновых фронтов может привести к переполнению кэша и снижению производительности.
Если у вас есть интервалы, в которых вы недоиспользуете GPU, вы можете использовать async compute для параллельного перемещения работы вычислительных шейдеров в очередь графики. Например, во время создания карты теней GPU выполняет рендеринг только по глубине. В этот момент происходит очень мало работы с пиксельными шейдерами, и многие волновые фронты остаются незанятыми.
Если вы сможете синхронизировать работу вычислительных шейдеров с рендерингом только глубины, это позволит лучше использовать GPU в целом. Неиспользованные волновые фронты могут помочь в работе с Screen Space Ambient Occlusion (SSAO) или любой другой задачей, дополняющей текущую работу.
Посмотрите сессию "Оптимизация производительности для консолей высокого класса " от Unite.
Одно из самых полных наших руководств, в котором собрано более 80 действенных советов по оптимизации игр для ПК и консолей. Эти подробные советы, созданные нашими экспертами в области Success и Accelerate Solutions, помогут вам получить максимальную отдачу от Unity и повысить производительность вашей игры.