tags: [vr-ar]
Single Pass Instanced rendering configuration for graphics acceleration
In Multi Pass rendering, each VR frame is drawn twice — separately for the left and right eye. Two complete passes through the entire render pipeline, two sets of draw calls, double CPU load. Single Pass Instanced solves this differently: one pass where geometry is instantiated for both eyes simultaneously through stereo instancing. The GPU processes two viewports in a single call.
On Meta Quest 3, transitioning from Multi Pass to Single Pass Instanced delivers 15% to 40% performance gains in CPU-heavy scenes. This is not hypothetical optimization — it's the first thing to do when porting a game to VR.
Why Simple enabling breaks shaders
Enabling Single Pass Instanced in Project Settings → XR Plugin Management is one checkbox. After enabling it, part of the shaders stops working correctly. This is not a configuration bug, it's expected behavior.
In Single Pass Instanced, a shader receives a stereo index (unity_StereoEyeIndex) — 0 for the left eye, 1 for the right. Projection and view matrices are also stored as arrays unity_StereoMatrixVP[2]. Shaders written without this in mind use only unity_MatrixVP — a single matrix for one eye — and visually work correctly only for one eye. The second either shifts or displays the first eye's image.
Surface Shaders in Built-in RP are automatically compatible — Unity adds the necessary macros during compilation. Custom Vertex/Fragment shaders require manual use of macros UNITY_MATRIX_MVP, UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX, UNITY_TRANSFER_STEREO_EYE_INDEX. Skip any of them — and one eye renders with artifacts.
In URP the situation is slightly cleaner: most built-in shaders are already compatible with Single Pass Instanced through stereo matrices in UnityPerDraw cbuffer. But custom render passes in ScriptableRendererFeature are often written without stereo awareness — especially if taken from tutorials for regular games.
Diagnosis via Frame Debugger
The first sign of Single Pass Instanced problems is artifacts strictly on one eye. In Frame Debugger visible: instead of one draw call with [Instanced: 2] there are two separate calls — this means instancing didn't apply to that specific object.
There are several causes. GPU Instancing disabled on the material — the simplest. Or the material uses MaterialPropertyBlock with data that differs per instance — this breaks standard instancing (a special instanced property in the shader is needed). Or the mesh exceeds vertex limits for dynamic batching — but that's a different path.
In URP with Forward Renderer, problems are most often in Post Processing. Effects like Depth of Field, Motion Blur, Bloom in most implementations don't support stereo directly — they apply to one renderTarget and get duplicated, causing visual shift. Solution — use VR Mode in Post Processing Volume, or for critical effects write stereo-compatible shaders manually.
Pitfalls with texture atlases and UV
Single Pass Instanced uses Texture2DArray for render targets of both eyes. This means any shader that samples _CameraDepthTexture or _CameraColorTexture must do so through SAMPLE_TEXTURE2D_ARRAY with the correct layer index (unity_StereoEyeIndex), not through standard SAMPLE_TEXTURE2D.
Custom post-processing effects written for normal rendering often sample _CameraDepthTexture as a regular 2D texture. In Single Pass Instanced this gives the depth of the left eye for both viewports. Visually: effects like SSAO or outline work correctly for the left eye, and for the right — shifted or absent entirely.
Transition process to Single Pass Instanced
Standard workflow: enable SPI → collect list of shaders with errors → prioritize by visibility → fix in sequence. Usually 70–80% of shaders work without changes, 15–20% need stereo macro additions, 5–10% need rewriting or alternatives.
Testing runs in parallel on two devices: one in Multi Pass (reference), the other in Single Pass Instanced. Compare each scene visually and via profiler.
| Project size | Number of custom shaders | Estimated timeline |
|---|---|---|
| Small (up to 10 custom shaders) | Up to 10 | 3–7 days |
| Medium | 10–30 | 1–3 weeks |
| Large (with post-processing and custom RP) | 30+ | 3–6 weeks |
Cost calculated after shader base audit and current render pipeline analysis.





