Documentation

Unity Engine


User Manual

Script Reference

Unity Engine


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
UNITY_VERTEX_INPUT_INSTANCE_ID
macro to the
appdata
struct.
Example:
struct appdata{ float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID //Insert};

Update the vertex output attributes struct

Add
UNITY_VERTEX_OUTPUT_STEREO
macro to the
v2f
output struct.
Example:
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
vert
method (in order):
  1. UNITY_SETUP_INSTANCE_ID()
  2. UNITY_INITIALIZE_OUTPUT(v2f, o)
  3. UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO()
UNITY_SETUP_INSTANCE_ID()
calculates and sets the built-in
unity_StereoEyeIndex
and
unity_InstanceID
shader variables to the correct values based on which eye the GPU is currently rendering.
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO
tells the GPU which eye in the texture array to render to, based on the value of
unity_StereoEyeIndex
. This macro also transfers the value of
unity_StereoEyeIndex
from the vertex shader so that it will be accessible in the fragment shader only if you call
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
in the fragment shader
frag
method.
UNITY_INITIALIZE_OUTPUT(v2f,o)
initializes all
v2f
values to 0.
Example:
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.
Note
You can download all Unity base shader scripts from the Unity website.
Do the following steps for each post-processing shader that you want to support single-pass instancing:
  1. Add the
    UNITY_DECLARE_SCREENSPACE_TEXTURE(tex)
    macro 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.
  2. Add
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i)
    at 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_StereoEyeIndex
    built-in shader variable to find out which eye the GPU is rendering to. This is useful when testing post-processing effects.
  3. Use the
    UNITY_SAMPLE_SCREENSPACE_TEXTURE()
    macro 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.
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
//Insert
marks the lines added to the shader code.
struct 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
CommandBuffer.SetInstanceMultiplier
. You don't need to manually adjust the instance count for standard instanced draws.
This automatic multiplier doesn't apply to indirect draw calls, because methods such as
Graphics.DrawProceduralIndirect()
and
CommandBuffer.DrawProceduralIndirect()
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.

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
unity_StereoEyeIndex
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:
Out = lerp(LeftColor, RightColor, unity_StereoEyeIndex);