Possible to use a quad-based geometry shader on a point mesh?

Hey there,

This is pretty much a duplicate of the “GPU Particles–Stuck on shader” post, but asks a more direct question. My issue seems to be that the original shader I’m transcoding uses a Geometry shader that spits out 4 verts at a time. They represent a “motion blur” quad that is used to render a single particle (and its trajectory). The mesh is drawn in point mode, since it’s just a cloud of vertices. I get very poor results using Graphics.DrawProcedural, and still pretty bad results (a.k.a. basically noise in both cases) when I use a MeshRenderer, which is capped at 65K verts.

Is it possible to do what I want to do? Here’s the shader code (which doesn’t work)

Shader "Smoke" {
Properties {
	_ShadowTex("Shadow (RGB) ", 2D) = "" {}
	_Color ("Main Color", Color) = (1,1,1,1)
	_PointRadius("Particle Radius", Range(0, 0.1)) = 0.005
	_DeltaTime("Delta Time", Range(0, 1)) = 0.015
	_centroid ("Centroid", Vector) = (0,0,0,0)
}
	
SubShader {
Pass{
	ZWrite Off ZTest Always Cull Off Fog { Mode Off }
	Blend SrcAlpha One

	CGPROGRAM
	#pragma target 5.0

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

	#include "UnityCG.cginc"
	
	fixed4 _Color;
	float4 _centroid;
	float _PointRadius;
	float _DeltaTime;

	StructuredBuffer<float4> particleBuffer;
	StructuredBuffer<float3> particleColor;
	
	struct vs_in {
		float4 pos : SV_POSITION;
		float4 col : COLOR;
		float4 uv : TEXCOORD0;
	};
	struct gs_in {
		float4 pos : SV_POSITION;
		float4 col : COLOR;
		float4 uv : TEXCOORD0;
		float4 uv2 : TEXCOORD1;
	};
	struct ps_in {
		float4 pos : SV_POSITION;
		float4 col : COLOR;
		float4 uv : TEXCOORD0;
		float4 uv2 : TEXCOORD1;
	};
	
	gs_in vert (vs_in input, uint id : SV_VertexID){
		gs_in output = (gs_in)0;
		float3 pos    = particleBuffer[id].xyz;
		float3 vel    = input.uv.xyz;
		float3 pos2   = (pos - vel*_DeltaTime).xyz;			// previous position
			
		output.pos = mul(UNITY_MATRIX_MV, float4(pos + _centroid, 1.0f));  // eye space
		output.uv = mul(UNITY_MATRIX_MV, float4(pos2, 1.0f));

		// aging
		float lifetime = input.uv.w;
		float age = input.pos.w;
		float phase = (lifetime > 0.0) ? (age / lifetime) : 1.0; // [0, 1]

		output.uv2.x = phase;
		float fade = 1.0 - phase;
		//  float fade = 1.0;

		//    gl_FrontColor = gl_Color;
		output.col = float4(input.col.xyz, input.col.w*fade);
		return output;
	}
	[maxvertexcount(3)]
	void geom(point gs_in input[1], inout TriangleStream<ps_in> triStream){
		ps_in output = (ps_in)0;

		// aging
		float phase = input[0].uv2.x;
		float radius = _PointRadius;
		
		// eye space
		float3 pos = input[0].pos.xyz;
		float3 pos2 = input[0].uv.xyz;
		float3 motion = pos - pos2;
		float3 dir = normalize(motion);
		float len = length(motion);
		
		float3 x = dir *radius;
		float3 view = normalize(-pos);
		float3 y = normalize(cross(dir, view)) * radius;
		float facing = dot(view, dir);

		// check for very small motion to avoid jitter
		float threshold = 0.01;
		
		if ((len < threshold) || (facing > 0.95) || (facing < -0.95)){
			pos2 = pos;
			x = float3(radius, 0.0, 0.0);
			y = float3(0.0, -radius, 0.0);
		}
		
		//output quad
		output.col =  input[0].col;
		
		output.uv = float4(0, 0, 0, phase);
		output.uv2 = input[0].pos;
		//output.pos = float4(pos + x + y, 1);
		output.pos = mul(UNITY_MATRIX_P,float4(pos + x + y, 1));
		triStream.Append(output);
		
		output.uv = float4(0, 1, 0, phase);
		output.uv2 = input[0].pos;
		//output.pos = float4(pos + x - y, 1);
		output.pos = mul(UNITY_MATRIX_P, float4(pos + x - y, 1));
		triStream.Append(output);
		
		output.uv = float4(1, 0, 0, phase);
		output.uv2 = input[0].pos;
		//output.pos = float4(pos2 - x + y, 1);
		output.pos = mul(UNITY_MATRIX_P, float4(pos2 - x + y, 1));
		triStream.Append(output);
		
		//output.uv = float4(1, 1, 0, phase);
		//output.uv2 = input[0].pos;
		//output.pos = float4(pos2 - x - y, 1);
		//output.pos = mul(UNITY_MATRIX_P, float4(pos2 - x - y, 1));
		//triStream.Append(output);
	}
	
	uniform sampler2D shadowTex;
	
	fixed4 frag (gs_in i ) : COLOR0 {
		float3 N;
		N.xy = i.uv.xy * float2(2.0, -2.0) + float2(-1.0, 1.0);
		float r2 = dot(N.xy, N.xy);
		
		if (r2 > 1.0) discard;		// kill pixels outside circle
		N.z = sqrt(1.0-r2);
		
		float4 eyeSpacePos = i.uv;
		float4 eyeSpaceSpherePos = float4(eyeSpacePos.xyz + N * _PointRadius, 1.0);	// point on sphere
		float4 shadowPos = mul(UNITY_MATRIX_TEXTURE0, eyeSpaceSpherePos);
		float3 shadow = float3(1, 1, 1) - tex2D(shadowTex, shadowPos.xyw).xyz;
		//float alpha = saturate(1.0 - r2);
		float alpha = clamp((1.0 - r2), 0.0, 1.0);
		alpha *= i.col.w;
		//return _Color;
		return float4(i.col.xyz *shadow * alpha, alpha);
	}
	
	ENDCG
	
	}
}

Fallback Off
}

I ended up getting this working. The trick is to use this call on the camera: Graphics.DrawProcedural(MeshTopology.Quads, particleCount);