tags: [vr-ar]
Настройка Single Pass Instanced рендеринга для ускорения графики
В Multi Pass рендеринге каждый фрейм VR отрисовывается дважды — отдельно для левого и правого глаза. Два полных прохода через весь рендер-пайплайн, два набора draw calls, двойная нагрузка на CPU. Single Pass Instanced решает это иначе: один проход, где геометрия инстансируется для обоих глаз одновременно через стерео-инстансинг. GPU обрабатывает два viewport за один вызов.
На Meta Quest 3 переход с Multi Pass на Single Pass Instanced даёт от 15% до 40% прироста производительности в CPU-heavy сценах. Это не гипотетическая оптимизация — это первое, что стоит сделать при портировании игры в VR.
Почему Simple переход ломает шейдеры
Включить Single Pass Instanced в Project Settings → XR Plugin Management — это одна галочка. После неё часть шейдеров перестаёт работать корректно. Это не баг настройки, это ожидаемое поведение.
В Single Pass Instanced шейдер получает стерео-индекс (unity_StereoEyeIndex) — 0 для левого глаза, 1 для правого. Матрицы проекции и вида тоже хранятся как массивы unity_StereoMatrixVP[2]. Шейдеры, написанные без учёта этого, используют только unity_MatrixVP — единственную матрицу для одного глаза — и визуально работают корректно только для одного глаза. Второй либо смещён, либо показывает картинку первого.
Surface Shaders в Built-in RP автоматически совместимы — Unity добавляет нужные макросы при компиляции. Кастомные Vertex/Fragment шейдеры требуют ручного использования макросов UNITY_MATRIX_MVP, UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX, UNITY_TRANSFER_STEREO_EYE_INDEX. Пропустить любой из них — и один глаз рендерится с артефактами.
В URP ситуация немного чище: большинство встроенных шейдеров уже совместимы с Single Pass Instanced через stereo matrices в UnityPerDraw cbuffer. Но кастомные рендер пассы в ScriptableRendererFeature часто написаны без учёта стерео — особенно если брались из туториалов для обычных игр.
Диагностика через Frame Debugger
Первый признак проблемы с Single Pass Instanced — артефакты строго на одном глазу. В Frame Debugger видно: вместо одного draw call с [Instanced: 2] идут два отдельных вызова — это значит, что инстансинг не применился для конкретного объекта.
Причин несколько. GPU Instancing выключен на материале — самое простое. Или материал использует MaterialPropertyBlock с данными, которые различаются на каждый инстанс — это ломает стандартный инстансинг (нужен специальный instanced property в шейдере). Или меш выходит за лимит вертексов для динамического батчинга — но это уже другой путь.
В URP с Forward Renderer проблемы чаще всего в Post Processing. Эффекты типа Depth of Field, Motion Blur, Bloom в большинстве реализаций не поддерживают стерео напрямую — они применяются к одному из renderTargets и дублируются, что даёт визуальный сдвиг. Решение — использовать VR Mode в Post Processing Volume, либо для критичных эффектов писать стерео-совместимые шейдеры вручную.
Подводные камни с текстурными атласами и UV
Single Pass Instanced использует Texture2DArray для рендер-таргетов обоих глаз. Это значит, что любой шейдер, который семплирует _CameraDepthTexture или _CameraColorTexture, должен делать это через SAMPLE_TEXTURE2D_ARRAY с правильным индексом слоя (unity_StereoEyeIndex), а не через обычный SAMPLE_TEXTURE2D.
Кастомные эффекты постобработки, написанные для обычного рендера, часто семплируют _CameraDepthTexture как обычную 2D-текстуру. В Single Pass Instanced это даёт глубину только левого глаза для обоих вьюпортов. Визуально: эффекты типа SSAO или outline работают правильно для левого глаза, а для правого — смещены или вовсе отсутствуют.
Процесс перехода на Single Pass Instanced
Стандартный план работы: включаем SPI → собираем список шейдеров с ошибками → приоритизируем по видимости → фиксируем по очереди. Обычно 70–80% шейдеров работают без изменений, 15–20% требуют добавления стерео-макросов, 5–10% нужно переписывать или искать альтернативу.
Тестирование ведётся параллельно на двух устройствах: одно в Multi Pass (эталон), второе в Single Pass Instanced. Сравниваем каждую сцену визуально и по профайлеру.
| Объём проекта | Количество кастомных шейдеров | Ориентировочные сроки |
|---|---|---|
| Небольшой (до 10 кастомных шейдеров) | До 10 | 3–7 дней |
| Средний | 10–30 | 1–3 недели |
| Крупный (с постобработкой и кастомным RP) | 30+ | 3–6 недель |
Стоимость рассчитывается после аудита шейдерной базы проекта и анализа текущего рендер-пайплайна.





