Invoking Geometry Shader for every vertex of a mesh

Hey guys,

I’m having issues getting geometry shaders to work properly.
I’m trying to implement a billboard particle shader using this code:

Shader Code

Shader "Custom/SpriteParticleTest"
{

   Properties
   {
     _Sprite("Sprite", 2D) = "white" {}
     _Color("Color", Color) = (0.38,0.26,0.98,1.0)
     _Size("Size", float) = 0.1
   }
   SubShader
   {
     Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
     Blend SrcAlpha One
     AlphaTest Greater .01
     ColorMask RGB
     Cull Off Lighting Off ZWrite Off

     Pass
     {
     

     CGPROGRAM
     #pragma target 5.0

     #pragma vertex vert
     #pragma geometry geom
     #pragma fragment frag

     #include "UnityCG.cginc"
     fixed4 _Color;
     float _Size;
     sampler2D _Sprite;
     
     struct appdata_t {
       float4 vertex : POSITION;
       fixed4 color : COLOR;
       float2 texcoord : TEXCOORD0;
     };
       
     struct v2g
     {
       float4 pos : SV_POSITION;
       fixed4 color : COLOR;
     };

     v2g vert(appdata_t v)
     {
       v2g OUT;
       OUT.pos = mul (UNITY_MATRIX_MVP, float4(v.vertex.xyz,1.0f));
       OUT.color = _Color;
       return OUT;
     }
     
     struct g2f {
       float4 pos : SV_POSITION;
       float2 uv : TEXCOORD0;
       fixed4 color : COLOR;
     };

     [maxvertexcount(4)]
     void geom(point v2g IN[1], inout TriangleStream<g2f> outStream)
     {
       float dx = _Size;
       float dy = _Size * _ScreenParams.x / _ScreenParams.y;
       g2f OUT;
       OUT.pos = IN[0].pos + float4(-dx, dy,0,0); OUT.uv=float2(0,0); OUT.color = IN[0].color; outStream.Append(OUT);
       OUT.pos = IN[0].pos + float4( dx, dy,0,0); OUT.uv=float2(1,0); OUT.color = IN[0].color; outStream.Append(OUT);
       OUT.pos = IN[0].pos + float4(-dx,-dy,0,0); OUT.uv=float2(0,1); OUT.color = IN[0].color; outStream.Append(OUT);
       OUT.pos = IN[0].pos + float4( dx,-dy,0,0); OUT.uv=float2(1,1); OUT.color = IN[0].color; outStream.Append(OUT);
       outStream.RestartStrip();
     }

     float4 frag (g2f IN) : COLOR
     {
         
       fixed4 col = 2.0f * IN.color * _Color * tex2D(_Sprite, IN.uv);
       return col;     
     }

     ENDCG

     }
   }

Fallback Off
}

The goal is to render a particle sprite for every vertex of the mesh that uses this shader. However the geometry shader doesn’t seem to get invoked for every vertex. When I create a material with this shader and attach it to a cube it produces the following output:

As you can see the geometry shader doesn’t output quads for some of the vertices. Also the lower right vertex seems to produce multiple quads (since it’s brighter and I’m using additive blending).

To me it looks like the GS somehow receives triangles as an input even though I specified “point” as the GS primitive. Maybe I’m totally wrong though.

Any help appreciated!

Now that you bring that up… I remember reading a thread where some guy had trouble with the triangle adjacency type. The conclusion was that unity does not properly set the input type in the rendering code and always sets triangles as the input type… making it the only geometry shader input type that seems to work, unfortunately. I’d probably go through the effort to actually test it out and send a bug report, but I’ve done that twice already for similar issues and the cases are still open to this day…

As a workaround, I suppose you could turn every vertex of every triangle into a quad and hope the overdraw does not become a problem.

Thanks for confirming, that this is most likely a bug. I will definitely send a bug report.

My workaround for now if anyone’s interested: I’m filling a ComputeBuffer with the vertex positions of my mesh and use Graphics.DrawProcedural(). You can specify MeshTopology.Points to pass the positions to the gpu, then read them from a StructuredBuffer in a vertex shader and pass them to the geometry shader.

I’m not sure how efficient that is though, since it comes with the overhead of creating the buffer and calling SetData() (potentially every frame for dynamic objects) on it. Works well with just one object with only a few hundred vertices.

1 Like

In case you do and they respond, please let me know, thanks!

@Dolkar I also never received an answer to my bug report, but the release notes of patch 5.2.2p2 contain some fixes that sound promising. If I have some more time, I wil test if my issue is fixed and post an update to this thread.

I know this is an old issue, but can anyone confirm that this has been resolved?

@castor76 I believe this works as intended, as explained here: Billboard Geometry Shader - Community Showcases - Unity Discussions

In short: the geometry shader expects points but the cube is made of triangles. There is no built-in Unity geometry made of points but you can create some in script easily. Something like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class geo1 : MonoBehaviour {

    void Start()
    {
        MeshFilter mf = GetComponent<MeshFilter>();

        mf.mesh = new Mesh();

        Vector3[] verts = new Vector3[2];
        verts[0].Set(0.0f, 0.0f, 0.0f);
        verts[1].Set(10.0f, 1.0f, 0.0f);
        mf.mesh.vertices = verts;

        // You can use UVs to pass information to the geometry shader
        Vector2[] uvs = new Vector2[2];
        uvs[0].x = 1.0f;
        uvs[0].y = 1.0f;
        uvs[1].x = 2.0f;
        uvs[1].y = 0.5f;
        mf.mesh.uv = uvs;

        int[] indices = new int[2];
        indices[0] = 0;
        indices[1] = 1;
        mf.mesh.SetIndices(indices, MeshTopology.Points, 0);
    }  
}
3 Likes