Is it possible to have GPU Instancing with Geometry shader?

I need to spawn a few objects with geometry shader. Is it possible to have GPU Instancing & geometry shader?
I didn’t see any example at unity site.

My source code approximatelly looks like this:

Properties {
        _MainTex ("Tex", 2D) = "white" {}

        _Color ("_Color", Color) = (0,1,1,1)
       
        _Burn("_Burn", Range(0,2)) = 0.0

// Geometry!
        _Factor ("Factor", Range(0., 20.)) = 20
    }

    SubShader {
        Tags {
            "Queue"="Geometry-2"
            "IgnoreProjector"="true"
            "RenderType"="Opaque"
        }
        LOD 100

        Zwrite             On// ff
        Lighting           Off
        Fog         { Mode Off }
        // Cull               Off


        Blend SrcAlpha OneMinusSrcAlpha
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            #pragma multi_compile_instancing//<======
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;

                UNITY_VERTEX_INPUT_INSTANCE_ID//<======
            };
            struct v2g {
                float4 vertex : POSITION;
                float2 uv  : TEXCOORD0;

                UNITY_VERTEX_INPUT_INSTANCE_ID//<======
            };
            struct g2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;

                UNITY_VERTEX_INPUT_INSTANCE_ID//<======
            };
            sampler2D _MainTex;
            fixed4 _Color;
            fixed _Burn;

            //??UNITY_INSTANCING_BUFFER_START(Props)
            //?    UNITY_DEFINE_INSTANCED_PROP(float, _Burn)
            //?UNITY_INSTANCING_BUFFER_END(Props)

// Geom:
            float _Factor;
          
            v2g vert (appdata v) {// appdata_base
                v2g o;

                UNITY_SETUP_INSTANCE_ID(v);
                // Transfer to fragment shader.
                //UNITY_TRANSFER_INSTANCE_ID(v, o);
                //UNITY_INITIALIZE_OUTPUT(v2g, o);
              //UNITY_SETUP_INSTANCE_ID(v);
                //UNITY_TRANSFER_INSTANCE_ID(v, o);
                //UNITY_INITIALIZE_OUTPUT(v2g, o);

                o.vertex = v.vertex;
                o.uv = v.texcoord;

                return o;
            }
            [maxvertexcount(24)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> tristream) {
                g2f o;

                // UNITY_SETUP_INSTANCE_ID(o);
                // UNITY_TRANSFER_INSTANCE_ID(IN, o);
                UNITY_INITIALIZE_OUTPUT(g2f, o);
                float3 edgeA = IN[1].vertex - IN[0].vertex;
                float3 edgeB = IN[2].vertex - IN[0].vertex;
                float3 normalFace = normalize(cross(edgeA, edgeB));

                float _factor = _Factor * _Burn;// UNITY_ACCESS_INSTANCED_PROP(Props, _Burn);
                for(int i = 0; i < 3; i++)
                {
                    o.pos = UnityObjectToClipPos(IN[i].vertex);
                    o.uv = IN[i].uv;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[i].vertex + float4(normalFace, 0) * _factor);
                    o.uv = IN[i].uv;
                    tristream.Append(o);
                    int inext = (i+1) % 3;
                    o.pos = UnityObjectToClipPos(IN[inext].vertex);
                    o.uv = IN[inext].uv;
                    tristream.Append(o);
                    tristream.RestartStrip();
                    o.pos = UnityObjectToClipPos(IN[i].vertex + float4(normalFace, 0) * _factor);
                    o.uv = IN[i].uv;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[inext].vertex);
                    o.uv = IN[inext].uv;
                    tristream.Append(o);
                    o.pos = UnityObjectToClipPos(IN[inext].vertex + float4(normalFace, 0) * _factor);
                    o.uv = IN[inext].uv;
                    tristream.Append(o);
                    tristream.RestartStrip();
                }
                for(int i = 0; i < 3; i++) {
                    o.pos = UnityObjectToClipPos(IN[i].vertex + float4(normalFace, 0) * _factor);
                    o.uv = IN[i].uv;
                    tristream.Append(o);
                }
                tristream.RestartStrip();
                for(int i = 0; i < 3; i++) {
                    o.pos = UnityObjectToClipPos(IN[i].vertex);
                    o.uv = IN[i].uv;
                    tristream.Append(o);
                }
                tristream.RestartStrip();
            }
          
            fixed4 frag (g2f i) : SV_Target {
                // Setup.
                UNITY_SETUP_INSTANCE_ID(i);

                return tex2D(_MainTex, i.uv) * _Color * _Burn;// UNITY_ACCESS_INSTANCED_PROP(Props, _Burn);
            }
            ENDCG
        }
    }

I don’t know what don’t work. I guess it’s relevant to UNITY_SETUP_INSTANCE_ID or UNITY_ACCESS_INSTANCED_PROP or UNITY_VERTEX_INPUT_INSTANCE_ID. I spent a big amont of time trying to find the problem, but unsuccessfully.

Would you tell me, what’s wrong? Guys, did you see some geometry shader+GPI Instancing examples?

Bump!
Nobody knows :confused: ???

It’s totally possible. But you have all of the lines you’d need to make it work commented out above for some reason. The vertex shader has the instance ID as an input. The main issue I see is you’re calling UNITY_INITIALIZE_OUTPUT after you’ve setup the instance ID, which blows the value you just set away. That UNITY_INITIALIZE_OUTPUT macro sets all values of the struct to 0.0. So you need to call that first, before you call UNITY_TRANSFER_INSTANCE_ID if you’re going to call it at all. Personally I never use it because it masks when I have an error someplace else, like having an unused value in the v2f, or in your case the v2g.

Minimal instanced geometry shader, with instanced properties in both the geometry shader and fragment shader stages.

Shader "Unlit/InstancedGeometryShader"
{
    Properties
    {
        _TriangleOffset ("Triangle Offset", Float) = 0.1
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2g
            {
                float4 worldPos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct g2f
            {
                float4 pos : SV_POSITION;
                float3 barycentric : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            v2g vert (appdata v)
            {
                v2g o;

                // set all values in the v2g o to 0.0
                UNITY_INITIALIZE_OUTPUT(v2g, o);

                // setup the instanced id to be accessed
                UNITY_SETUP_INSTANCE_ID(v);

                // copy instance id in the appdata v to the v2g o
                UNITY_TRANSFER_INSTANCE_ID(v, o);

                o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
                return o;
            }

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float, _TriangleOffset)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)

            [maxvertexcount(24)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> tristream)
            {
                g2f o;

                // set all values in the g2f o to 0.0
                UNITY_INITIALIZE_OUTPUT(g2f, o);

                // setup the instanced id to be accessed
                UNITY_SETUP_INSTANCE_ID(IN[0]);

                // copy instance id in the v2f IN[0] to the g2f o
                UNITY_TRANSFER_INSTANCE_ID(IN[0], o);

                // access instanced property
                float triOffset = UNITY_ACCESS_INSTANCED_PROP(Props, _TriangleOffset);

                // explode triangles by each triangle's actual surface normal
                float3 triNormal = normalize(cross(IN[0].worldPos.xyz - IN[1].worldPos.xyz, IN[0].worldPos.xyz - IN[2].worldPos.xyz)) * triOffset;

                o.pos = UnityWorldToClipPos(IN[0].worldPos.xyz + triNormal);
                o.barycentric = float3(1,0,0);
                tristream.Append(o);

                o.pos = UnityWorldToClipPos(IN[1].worldPos.xyz + triNormal);
                o.barycentric = float3(0,1,0);
                tristream.Append(o);

                o.pos = UnityWorldToClipPos(IN[2].worldPos.xyz + triNormal);
                o.barycentric = float3(0,0,1);
                tristream.Append(o);
            }

            fixed4 frag (g2f i) : SV_Target
            {
                // setup instance id to be accessed
                UNITY_SETUP_INSTANCE_ID(i);

                // access instanced property
                fixed4 col = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
                col.rgb *= i.barycentric;

                return col;
            }
            ENDCG
        }
    }
}
7 Likes

Tkank you SO much!!!

Thanks really useful!

To add on to this problem, I’m trying to reconfigure this script to support VR for single passed instanced rendering in the built-in renderer. But I can’t figure out a way to show it in both eyes. The script is not made by me, its free and got it from here : https://www.patreon.com/posts/53587750.

Shader "Custom/GrassForCompute"
{
    Properties
    {
        [Toggle(FADE)] _TransparentBottom("Transparency at Bottom", Float) = 0
        _Fade("Fade Multiplier", Range(1,10)) = 6
    }

        CGINCLUDE
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile _SHADOWS_SCREEN
#pragma multi_compile_fwdbase_fullforwardshadows
#pragma multi_compile_fog
#pragma shader_feature FADE

    struct appdata
    {
      

        UNITY_VERTEX_INPUT_INSTANCE_ID
    };

        struct DrawVertex
    {
        float3 positionWS; // The position in world space
        float2 uv;
        float3 diffuseColor;
    };

    // A triangle on the generated mesh
    struct DrawTriangle
    {
        float3 normalOS;
        DrawVertex vertices[3]; // The three points on the triangle
    };

    StructuredBuffer<DrawTriangle> _DrawTriangles;

    struct v2f
    {
        float4 pos : SV_POSITION; // Position in clip space
        float2 uv : TEXCOORD0;          // The height of this vertex on the grass blade
        float3 positionWS : TEXCOORD1; // Position in world space
        float3 normalWS : TEXCOORD2;   // Normal vector in world space
        float3 diffuseColor : COLOR;
        LIGHTING_COORDS(3, 4)
            UNITY_FOG_COORDS(5)
            UNITY_VERTEX_OUTPUT_STEREO
          
    };

    // Properties
    float4 _TopTint;
    float4 _BottomTint;
    float _AmbientStrength;
    float _Fade;

    // Vertex function
    struct unityTransferVertexToFragmentSucksHack
    {
       
        float3 vertex : POSITION;
    };

    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

      
        return o;
    }
    // -- retrieve data generated from compute shader
    v2f vert(uint vertexID : SV_VertexID)
    {
      
        // Initialize the output struct
        v2f output = (v2f)0;
       
        // Get the vertex from the buffer
        // Since the buffer is structured in triangles, we need to divide the vertexID by three
        // to get the triangle, and then modulo by 3 to get the vertex on the triangle
        DrawTriangle tri = _DrawTriangles[vertexID / 3];
        DrawVertex input = tri.vertices[vertexID % 3];

        output.pos = UnityObjectToClipPos(input.positionWS);
        output.positionWS = input.positionWS;

        // float3 faceNormal = GetMainLight().direction * tri.normalOS;
        float3 faceNormal = tri.normalOS;
        // output.normalWS = TransformObjectToWorldNormal(faceNormal, true);
        output.normalWS = faceNormal;

        output.uv = input.uv;

        output.diffuseColor = input.diffuseColor;

        // making pointlights work requires v.vertex
        unityTransferVertexToFragmentSucksHack v;
        v.vertex = output.pos;

        TRANSFER_VERTEX_TO_FRAGMENT(output);
        UNITY_TRANSFER_FOG(output, output.pos);

        return output;
    }




    ENDCG
        SubShader
    {
        Cull Off
        Blend SrcAlpha OneMinusSrcAlpha // for the transparency
        Pass // basic color with directional lights
        {
            Tags
            {
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag     
            #include "UnityCG.cginc"

            float4 frag(v2f i) : SV_Target
            {
           
            // take shadow data
            float shadow = 1;
            #if defined(SHADOWS_SCREEN)
                shadow = (SAMPLE_DEPTH_TEXTURE_PROJ(_ShadowMapTexture, UNITY_PROJ_COORD(i._ShadowCoord)).r);
            #endif           
                // base color by lerping 2 colors over the UVs
                float4 baseColor = lerp(_BottomTint , _TopTint , saturate(i.uv.y)) * float4(i.diffuseColor, 1);
                // multiply with lighting color
                float4 litColor = (baseColor * _LightColor0);
                // multiply with vertex color, and shadows
                float4 final = litColor;
                final.rgb = litColor * shadow;
                // add in baseColor when lights turned off
                final += saturate((1 - shadow) * baseColor * 0.2);
                // add in ambient color
                final += (unity_AmbientSky * baseColor * _AmbientStrength);

                // add fog
                UNITY_APPLY_FOG(i.fogCoord, final);
                // fade the bottom based on the vertical uvs
                #if FADE
                    float alpha = lerp(0, 1, saturate(i.uv.y * _Fade));
                    final.a = alpha;
                #endif
                return final;
            }
            ENDCG
        }

        Pass
                // point lights
                {
                    Tags
                    {
                        "LightMode" = "ForwardAdd"
                    }
                    Blend OneMinusDstColor One
                    ZWrite Off
                    Cull Off

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag                                   
                    #pragma multi_compile_fwdadd_fullforwardshadows
                    #include "UnityCG.cginc"

                    float4 frag(v2f i) : SV_Target
                    {
                       
                        UNITY_LIGHT_ATTENUATION(atten, i, i.positionWS);

            // base color by lerping 2 colors over the UVs
            float3 baseColor = lerp(_BottomTint , _TopTint , saturate(i.uv.y)) * i.diffuseColor;

            float3 pointlights = atten * _LightColor0.rgb * baseColor;
            #if FADE
                float alpha = lerp(0, 1, saturate(i.uv.y * _Fade));
                pointlights *= alpha;
            #endif

            return float4(pointlights, 1);
        }
        ENDCG
    }

    Pass // shadow pass
    {

        Tags
        {
            "LightMode" = "ShadowCaster"
        }

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_shadowcaster
         #include "UnityCG.cginc"

        float4 frag(v2f i) : SV_Target
        {
          
            SHADOW_CASTER_FRAGMENT(i)
        }
        ENDCG
    }


    }        Fallback "VertexLit"
}

There are also examples at

Thank you @bgolus ! I just started my shader learning journey and I already stumbled across many very helpful hints from you in the forums. Thank you so much!

I’m trying to implement geometry shader, for low-poly mesh voxelization and I guess it will be helpful for voxel instancing. thanks!

I’m trying to port a geometry shader to Single Pass Instanced render mode. I’m using the macros mentioned above, also tried to use UNITY_VERTEX_OUTPUT_STEREO, UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO and UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX with no luck.
Is there a way to use geometry shader with Single Pass Instanced render mode?