Представьте ситуацию: на демо клиент испытывает VR-тренажер «Работы на высоте».

Легкий ветерок, стальной пролет, панорама города. Красота. Клиент поднимается по лестнице, останавливается на краю и с восхищением говорит: «Как круто вы сделали, что от вида вниз у меня голова закружилась!» Мы переглядываемся. Потому что «круто» — это не мы сделали. Это заслуга плохой оптимизации раннего прототипа.

Пока персонаж карабкался, движок героически пытался «на лету» подгрузить пачку тяжелых моделей. FPS просел, рендер начал задыхаться, и вестибулярка клиента объявила забастовку. Иммерсивность —10/10, комфорт — где-то в районе отрицательных значений. Если голова кружится, это должно быть запланировано геймдизайнером, а не видеокартой. 

Привет, я backend-разработчик SimbirSoft Андрей. В этой статье разберем, как сделать так, чтобы VR-проекты на Unity работали стабильно и были дружелюбны к вестибулярному аппарату игрока.

Кадры решают всё

Основное понятие здесь — FPS (Frames Per Second, частота кадров). Это число кадров, которые игра или приложение отображает на экране каждую секунду. Чем выше FPS, тем плавнее движется картинка и быстрее отклик на действия игрока.

В VR стабильная частота кадров — это не просто метрика производительности, а основа комфорта игрока. 

Если в обычной игре кратковременное падение кадров с 90 до 60 игрок может и не заметить, то в VR сбой кадров даже на долю секунды создает рассинхрон между движением и изображением. Мозг получает противоречивые сигналы: к примеру глаза видят движение, а вестибулярный аппарат сообщает, что тело стоит на месте. В результате возникает дискомфорт, головокружение и то самое ощущение укачивания.

Для разных VR-устройств целевые значения FPS отличаются из-за аппаратных ограничений и особенностей дисплеев. Поэтому во время разработки важно проводить тесты производительности на минимальных по характеристикам гарнитурах из списка целевых, чтобы убедиться, что сцена будет стабильно работать даже на самом слабом устройстве с ориентированием на мощность процессора и возможности частот обновления дисплеев. Мы в свое время пытались «на глаз» понять, где у нас узкие места, и это было ошибкой. С тех пор правило одно: Unity Profiler — твой лучший друг.

Кто виноват и что делать

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

  • CPU отвечает за выполнение игровой логики, обработку ввода, расчет анимаций, физики, а также подготовку команд на отрисовку объектов (draw calls). 

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

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

Отследить, кто именно является узким местом, можно через Unity Profiler: если GPU Frame Time выше, чем CPU Frame Time, значит вы упираетесь в графическую производительность. Если наоборот, то проблема в логике и подготовке сцены для отрисовки.

На скриншоте профайлер открыт с аналитикой работы CPU:

  • Main Thread отвечает за игровую логику: расчеты физики, анимаций и других систем. Он формирует, какие объекты и как должны быть отрисованы (команды рендера), но не занимается низкоуровневой подготовкой данных для GPU.

  • Render Thread берет команды, подготовленные Main Thread, и выполняет низкоуровневую подготовку для GPU: переносит данные в буфер, сортирует объекты по материалам и шейдерам, устанавливает состояния рендера (render state changes).

Если CPU не успевает подготовить кадр (расчёты физики, draw calls, обработку объектов и т.д.), в Render Thread появляются маркеры:

Gfx.WaitForGfxCoZmmandsFromMainThread

Semaphore.WaitForSignal

GPU при этом не работает в полную силу, а чилит, как разработчик, который допивает третий кофе и ждет, пока тимлид наконец добавит задачу в Jira.

Пример того, на что можно обратить внимание при такой ситуации: 

1. Сокращение количества вызовов рендеринга (Draw Calls)

  • Использовать Static Batching для неподвижных объектов.

  • Применять Dynamic Batching (только для мелких объектов, т.к. может съесть память).

  • GPU Instancing для повторяющихся моделей.

  • Комбинировать мелкие меши в один при загрузке сцены.

2. Оптимизация скриптовой логики

  • Минимизировать код в Update() — выносить логику в события, OnEnable/OnDisable или InvokeRepeating.

  • Кешировать ссылки на компоненты (GetComponent — только при инициализации).

  • Избегать частого выделения памяти (гарbage collector), использовать List.Clear() вместо пересоздания.

  • Переносить тяжелые операции на несколько кадров (корутины или UniTask).

  • Применять Job System + Burst Compiler для параллельных вычислений.

3. Оптимизация физики

  • Уменьшить Fixed Timestep в Project Settings.

  • Использовать простые коллайдеры (Capsule, Box) вместо Mesh Collider.

  • Отключать коллайдеры и Rigidbody у неактивных объектов.

  • Уменьшать количество слоев для физики (Layer Collision Matrix).

  • Объединять триггеры и зоны детекции в крупные объекты.

4. Оптимизация анимаций и IK

  • Отключать анимацию для невидимых объектов (Animator.cullingMode = CullCompletely).

  • Запекать сложные анимации и не использовать IK там, где можно заранее запечь позу.

  • Оптимизировать скелет 3D модели: удалить лишние кости, оставив только необходимые для анимации.

5. Управление обновлением объектов

  • Использовать Object Pooling вместо Instantiate / Destroy.

  • Отключать неиспользуемые объекты (SetActive(false)), а не уничтожать.

  • Делить обновление на группы: часть объектов обновляется раз в 2-3 кадра.

Если же наоборот Main Thread быстро формирует команды, Render Thread также шустро подготавливает их для GPU, а видеокарта не успевает отрисовать кадр, то уже CPU, подготовив все необходимое, ждет освобождения GPU, чтобы передать ему новые данные. В Profiler в таком случае мы можем увидеть флаг Gfx.WaitForPresent. Это значит, что CPU простаивает, пока видеокарта заканчивает текущую работу.

На скриншоте информация чем занят GPU:

  • Растеризацией геометрии (превращение трехмерных моделей в набор пикселей на экране).

  • Вычислением вершинных и пиксельных шейдеров (освещение, тени, материалы, спецэффекты).

  • Постобработкой (Bloom, Depth of Field, Motion Blur и т. п.).

  • Отрисовкой прозрачных объектов и сложных материалов.

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

  • Сборкой кадра в буфере и подготовкой его к выводу на экран (V-Sync, буферизация).

В этом случае оптимизация смещается на уменьшение визуальной нагрузки:

1. Оптимизация геометрии

  • Сократить количество полигонов, внедрить LOD-системы (LOD Group).

  • Объединить меши для снижения количества draw calls.

  • Использовать Occlusion Culling для отбрасывания невидимых объектов.

2. Оптимизация текстур

  • Снизить разрешение и битность текстур.

  • Использовать сжатия (DXT, ASTC, ETC2 в зависимости от платформы).

  • Использовать атласы для UI и мелких объектов.

3. Оптимизация шейдеров

  • Минимизировать количество ветвлений (if в шейдерах).

  • Убирать ненужные функции (например, Parallax Mapping, если он не критичен).

  • Переходить с Surface Shader на простые Vertex/Fragment, если возможно.

  • Использовать Shader LOD, отключая тяжелые эффекты на слабых устройствах.

4. Оптимизация освещения

  • Бейкить статическое освещение (Baked GI) вместо динамического.

  • Использовать Light Probes для динамических объектов.

  • Уменьшить количество динамических источников света.

  • Сократить дальность и интенсивность теней, использовать мягкие тени только там, где это критично.

5. Оптимизация постобработки

  • Убирать дорогие эффекты (Bloom, SSAO, DOF) или использовать упрощённые версии.

  • Объединять несколько постэффектов в один шейдер (Custom Render Pass).

  • Снижать разрешение рендера постобработки (Half Resolution для SSAO, DOF).

6. Оптимизация прозрачности

  • Минимизировать количество прозрачных объектов (overdraw).

  • Сортировать прозрачные объекты по расстоянию и отбрасывать невидимые.

  • Использовать Cutout вместо Alpha Blend там, где возможно.

7. Масштабирование разрешения рендера

  • Внедрить динамическое масштабирование рендера (Dynamic Resolution Scaling).

  • Использовать TAAU (Temporal Anti-Aliasing Upsample) или FSR.

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

Укрощение диких кадров

Много кадров — это, конечно, хорошо, но даже при 140 FPS одна просадка до 100 превращается в микролаги, и ваш мозг запускает внутренний debugger в лице вестибулярного аппарата, который сообщает о проблемах и эвакуирует содержимое желудка. Что же можно предпринять для исключения подобных ситуаций?

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

Вот краткий список простых приемов для снижения пиковых всплесков:

  • Разбивать тяжёлые операции (например, загрузку ассетов или генерацию данных) на несколько кадров с помощью Job System или Coroutines.

  • Иногда полезно зафиксировать частоту на чуть ниже максимальной стабильной (например, вместо нестабильных 140 FPS-ровные 120 FPS). В Unity можно управлять этим через Application.targetFrameRate или Vsync.

  • Избегать GC (Garbage Collector) spikes — следить за аллокациями в Update, использовать пул объектов.

  • Минимизировать частые вызовы FindObjectOfType, GetComponent и других тяжёлых операций.

  • Оптимизировать физику: уменьшить количество объектов с RigidBody/Collider, настроить Fixed Timestep в Project Settings.

  • Избегать внезапных «тяжелых» эффектов (например, взрывов с множеством частиц в один кадр).

  • Снизить дальность и качество теней, если они просаживают производительность в пиковых сценах.

  • Загружать модели, текстуры и анимации до их появления в кадре. Для этого можно использовать Addressables и LoadAssetAsync с буферизацией.

  • Если один из компонентов простаивает, перераспределить работу (например, часть расчетов с CPU на GPU через compute shaders или наоборот).

Два глаза — два бюджета (временного бюджета кадра)

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

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

Рассмотрим два режима рендера для такого случая в Unity.

Single Pass Instanced (SPI) позволяет рендерить сразу оба глаза за один проход шейдера с использованием GPU-инстансинга. Шейдер получает данные о положении обоих глаз и формирует стереопару в одном draw call.

Плюсы:

  • Существенно снижает количество draw calls.

  • Снижает нагрузку на CPU и Render Thread.

  • Хорошо поддерживается большинством кастомных шейдеров на PC.

Минусы:

  • Требует поддержки макросов UNITY_VERTEX_INPUT_INSTANCE_ID и UNITY_SETUP_INSTANCE_ID.

  • Некоторые постпроцессинг-эффекты могут работать некорректно.

Когда использовать:

  • PC и современные VR-шлемы с сложными шейдерами.

  • Когда важно снизить нагрузку на CPU и Render Thread.

Multi-View — режим, который заточен больше под мобильные платформы. GPU рендерит оба глаза за один проход, используя возможности драйвера и графического API.

Плюсы:

  • Минимальные требования к шейдерам.

  • Хорошо подходит для мобильного VR.

  • Снижает нагрузку на CPU и Render Thread.

Минусы:

  • Ограниченная поддержка платформ.

  • Иногда сложнее дебажить кастомные эффекты.

  • На PC эффективность зависит от драйвера.

Когда использовать:

  • Мобильные VR-платформы, где поддерживается OpenXR / Vulkan.

  • Сценарии с простыми шейдерами, когда нужно минимизировать draw calls.

Как включить в Unity:

  1. Открыть Project Settings → XR Plug-in Management → [ваш провайдер XR].

  2. В разделе Stereo Rendering Mode выбрать Single Pass Instanced.

Как не укачать игрока

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

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

Любые неожиданные скачки или рывки в кадре крайне нежелательны. Если камера движется «обоснованно» для игрока, например, он сидит на вагонетке, которая сама катится по рельсам, изменения угла обзора должны быть плавными и предсказуемыми. Резкие движения повышают риск укачивания и нарушают ощущение присутствия.

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

Методы передвижения:

Телепорт — самый комфортный и безопасный способ перемещения, особенно для новичков. Он позволяет игроку мгновенно менять положение в виртуальном пространстве, не вызывая дискомфорта вестибулярного аппарата. Конечно, в идеале игрок мог бы передвигаться самостоятельно в реальном пространстве, но, поскольку у большинства пользователей нет свободного «футбольного поля» под рукой, приходится прибегать к виртуальному перемещению.

Smooth locomotion — более «сложный» вариант для вестибулярного аппарата. Здесь игрок перемещается плавно (отклоняя, к примеру, трек джойстика), как при обычной ходьбе, но при этом мозг получает визуальный сигнал движения, а тело остается на месте. Этот разрыв между ощущением движения глазами и отсутствием физического перемещения вызывает эффект укачивания. Чтобы снизить дискомфорт, Smooth locomotion стоит использовать с регулируемой скоростью и предоставлять дополнительные опции для чувствительных к укачиванию пользователей-например, виньетку при движении или ограничение ускорений.

Фиксированные визуальные ориентиры в VR помогают мозгу удерживать положение в пространстве и значительно снижают риск укачивания. К таким ориентирам относятся:

Кабина или cockpit — внутреннее пространство транспорта (самолёта, вагонетки, машины), которое остается стабильным при движении. Кабина создает ощущение «опоры» для глаз и вестибулярного аппарата.

Шлем или элементы HUD — виртуальные интерфейсы, рамки или приборные панели, которые всегда находятся в поле зрения игрока. Они дают мозгу стабильные точки отсчёта при движении и поворотах.

Нос персонажа или virtual nose — тонкая линия или полупрозрачная форма, которая имитирует положение собственного носа игрока. Этот прием помогает мозгу ориентироваться и снижает эффект укачивания, особенно при быстром повороте камеры.

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

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

Вместо итогов

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

Спасибо за внимание!

Больше авторских материалов для backend-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.

Комментарии (1)


  1. lexnext1
    15.12.2025 21:33

    Спасибо за статью!

    В основном разработку под vr делаете на pc?

    Какую vr-гарнитуру под мак посоветуете для пробы пера в vr-unity?