tags: [vr-ar]
Налаштування Single Pass Instanced рендеринга для прискорення графіки
У Multi Pass рендерингу кожен VR кадр рисується двічі — окремо для лівого та правого ока. Два повних проходи через весь рендер-pipeline, два набори draw calls, подвійне навантаження на CPU. Single Pass Instanced вирішує це інакше: один прохід, де геометрія інстансується для обох очей одночасно через stereo instancing. GPU обробляє два viewport за один вызов.
На Meta Quest 3 перехід з Multi Pass на Single Pass Instanced дає від 15% до 40% приросту продуктивності в CPU-heavy сценах. Це не гіпотетична оптимізація — це перше, що треба зробити при портуванні гри в VR.
Чому простий перехід ламає шейдери
Включити Single Pass Instanced у Project Settings → XR Plugin Management — це один чекбокс. Після цього частина шейдерів перестає працювати коректно. Це не баг налаштування, це очікувана поведінка.
У Single Pass Instanced шейдер отримує stereo-індекс (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. Але кастомні рендер pass-и у ScriptableRendererFeature часто написані без урахування stereo — особливо якщо взяті з посібників для звичайних ігор.
Діагностика через Frame Debugger
Перший ознака проблем з Single Pass Instanced — артефакти суворо на одному оці. У Frame Debugger видно: замість одного draw call з [Instanced: 2] йдуть два окремих вызови — це означає, що instancing не застосувався до конкретного об'єкту.
Причин кілька. GPU Instancing вимкнена на матеріалі — найпростіше. Або матеріал використовує MaterialPropertyBlock з даними, які відрізняються для кожного інстансу — це ламає стандартний instancing (потрібна спеціальна instanced property у шейдері). Або mesh виходить за межу вертексів для динамічного батчингу — але це вже інший шлях.
У URP з Forward Renderer проблеми частіше за все у Post Processing. Ефекти типу Depth of Field, Motion Blur, Bloom у більшості реалізацій не підтримують stereo безпосередньо — вони застосовуються до одного з renderTargets і дублюються, що дає візуальний зсув. Рішення — використовувати VR Mode у Post Processing Volume, або для критичних ефектів писати stereo-сумісні шейдери вручну.
Підводні камені з текстурними атласами та UV
Single Pass Instanced використовує Texture2DArray для рендер-таргетів обох очей. Це означає, що будь-який шейдер, який семплює _CameraDepthTexture або _CameraColorTexture, мусить робити це через SAMPLE_TEXTURE2D_ARRAY з правильним індексом шару (unity_StereoEyeIndex), а не через звичайний SAMPLE_TEXTURE2D.
Кастомні ефекти постобробки, написані для звичайного рендеру, часто семплюють _CameraDepthTexture як звичайну 2D-текстуру. У Single Pass Instanced це дає глибину тільки лівого ока для обох viewport-ів. Візуально: ефекти типу SSAO або outline працюють правильно для лівого ока, а для правого — зміщені або зовсім відсутні.
Процес переходу на Single Pass Instanced
Стандартний план роботи: включаємо SPI → збираємо список шейдерів з помилками → пріоритизуємо за видимістю → фіксимо послідовно. Звичайно 70–80% шейдерів працюють без змін, 15–20% потребують додавання stereo-макросів, 5–10% потребують переписування або альтернатив.
Тестування ведеться паралельно на двох пристроях: один у Multi Pass (еталон), другий у Single Pass Instanced. Порівнюємо кожну сцену візуально та за профайлером.
| Обсяг проекту | Кількість кастомних шейдерів | Орієнтовні строки |
|---|---|---|
| Невеликий (до 10 кастомних шейдерів) | До 10 | 3–7 днів |
| Середній | 10–30 | 1–3 тижні |
| Великий (з постобробкою та кастомним RP) | 30+ | 3–6 тижнів |
Вартість розраховується після аудиту шейдерної бази проекту та аналізу поточного рендер-pipeline.





