Single-pass instanced rendering and custom shaders
Update custom shaders to support single-pass stereo instanced rendering.
Read time 6 minutesLast updated 20 days ago
Unity's Universal Render Pipeline (URP), High Definition Render Pipeline (HDRP), Shader Graph (except in the built-in render pipeline), Surface shaders, and built-in shaders already support single-pass stereo instanced rendering. However, shaders from the Asset Store, from other third parties, or those that you have written yourself might need updating.
For more information about supporting instanced rendering in your shaders, refer to GPU Instancing. The information in this section specifically talks about stereo rendering and might not include all changes you need to make to support instanced rendering in general.
Update your custom shader
To support single-pass stereo instanced rendering in a custom shader, update the vertex input and output attributes structs, and the main vertex shader function.
Update the vertex input attributes struct
Add the macro to the struct.
UNITY_VERTEX_INPUT_INSTANCE_IDappdataExample:
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID //Insert};
Update the vertex output attributes struct
Add macro to the output struct.
UNITY_VERTEX_OUTPUT_STEREOv2fExample:
struct v2f{ float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; UNITY_VERTEX_OUTPUT_STEREO //Insert};
Update the main vertex shader function
Add the following macros to the beginning of your main method (in order):
vertUNITY_SETUP_INSTANCE_ID()UNITY_INITIALIZE_OUTPUT(v2f, o)UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO()
UNITY_SETUP_INSTANCE_ID()unity_StereoEyeIndexunity_InstanceIDUNITY_INITIALIZE_VERTEX_OUTPUT_STEREOunity_StereoEyeIndexunity_StereoEyeIndexUNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEXfragUNITY_INITIALIZE_OUTPUT(v2f,o)v2fExample:
v2f vert (appdata v){ v2f o; UNITY_SETUP_INSTANCE_ID(v); //Insert UNITY_INITIALIZE_OUTPUT(v2f, o); //Insert UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //Insert o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o;}
Post-processing shaders
To enable single-pass stereo instancing for post-processing shaders, follow the steps for custom shaders. Once you have updated the vertex input and output attributes structs, and the main vertex shader function, do the following.
Do the following steps for each post-processing shader that you want to support single-pass instancing:
-
Add themacro outside the frag method (refer to the following example for placement) in your Shader script, so that when you use a particular stereo rendering method the GPU uses the appropriate texture sampler. For example, if you use multi-pass rendering, the GPU uses a texture 2D sampler. For single-pass instancing or multi-view rendering, the texture sampler is a texture array.
UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) -
Addat the beginning of the fragment shader frag method (refer to the following example for placement). You only need to add this macro if you want to use the
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i)built-in shader variable to find out which eye the GPU is rendering to. This is useful when testing post-processing effects.unity_StereoEyeIndex -
Use themacro when sampling 2D textures (refer to the following example). Standard shaders use a 2D texture-based back buffer to sample textures. Single-pass stereo instancing doesn't use this type of back buffer, so if you don't specify a different method for 2D texture sampling, your shader doesn't render correctly. To prevent rendering issues, the
UNITY_SAMPLE_SCREENSPACE_TEXTURE()macro detects which stereo rendering path you're using and then automatically samples the texture in the correct manner. Refer to Import a file from the shader library in the Built-In Render Pipeline to learn more about similar macros used for depth textures and screen-space shadow maps.UNITY_SAMPLE_SCREENSPACE_TEXTURE()
Example:
UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex); //Insertfixed4 frag (v2f i) : SV_Target{ UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); //Insert fixed4 col = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv); //Insert // just invert the colors col = 1 - col; return col;}
Full sample shader code
The following is a simple example of the template image effect shader with all the previously mentioned changes applied to support single-pass stereo instancing. The comment marks the lines added to the shader code.
//Insertstruct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID //Insert};//v2f output structstruct v2f{ float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; UNITY_VERTEX_OUTPUT_STEREO //Insert};v2f vert (appdata v){ v2f o; UNITY_SETUP_INSTANCE_ID(v); //Insert UNITY_INITIALIZE_OUTPUT(v2f, o); //Insert UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //Insert o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o;}UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex); //Insertfixed4 frag (v2f i) : SV_Target{ UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); //Insert fixed4 col = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv); //Insert // invert the colors col = 1 - col; return col;}
Procedural geometry
When you use single-pass instanced rendering, URP automatically doubles the instance count for regular draw calls in relevant render passes, by calling . You don't need to manually adjust the instance count for standard instanced draws.
CommandBuffer.SetInstanceMultiplierThis automatic multiplier doesn't apply to indirect draw calls, because methods such as and read their instance count from an arguments buffer rather than from a draw call parameter. For indirect draw calls, you must manually double the instance count contained in your compute buffer to support single-pass instancing.
Graphics.DrawProceduralIndirect()CommandBuffer.DrawProceduralIndirect()Debugging your shader
The following shader code renders a GameObject as green for a user’s left eye and red for their right eye. This shader is useful for debugging your stereo rendering, because it allows you to verify that all stereo graphics work and are functioning correctly.
Shader "XR/StereoEyeIndexColor"{ Properties { _LeftEyeColor("Left Eye Color", COLOR) = (0,1,0,1) _RightEyeColor("Right Eye Color", COLOR) = (1,0,0,1) } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag float4 _LeftEyeColor; float4 _RightEyeColor; #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_OUTPUT(v2f, o); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); return lerp(_LeftEyeColor, _RightEyeColor, unity_StereoEyeIndex); } ENDCG } }}
Shader Graph debug shader
Shader Graph automatically adds the macros required to support single-pass stereo rendering. To implement the debug shader in Shader Graph you can use a Custom Function node that sets the base color based on the eye index.

A custom function node in Shader Graph.
Use the shader attribute to determine the base color depending on which eye instance the GPU renders. The Custom Function node in the previous example contains the following code:
unity_StereoEyeIndexOut = lerp(LeftColor, RightColor, unity_StereoEyeIndex);