How to draw many circles efficiently

Discussion in 'Shaders' started by VictorKs, Oct 31, 2019.

1. VictorKs

Joined:
Jun 2, 2013
Posts:
148
What is the best way to draw thousands of circles with minimum cpu. I need them for a unity selection system they have to be rendered on units feet.

2. Przemyslaw_Zaworski

Joined:
Jun 9, 2017
Posts:
188
Example of rendering one milion blue circles with single draw call (add CS cript to main camera).

Code (CSharp):
1. using UnityEngine;
2.
3. public class DrawCircles : MonoBehaviour
4. {
6.     protected Material material;
7.
8.     void Start()
9.     {
11.     }
12.
13.     void OnRenderObject()
14.     {
15.         material.SetPass(0);
16.         Graphics.DrawProcedural(MeshTopology.Triangles, 6 * 1000000, 1);
17.     }
18. }
19.

Code (CSharp):
2. {
4.     {
5.         Pass
6.         {
7.             Cull Off
8.             CGPROGRAM
9.             #pragma vertex VSMain
10.             #pragma fragment PSMain
11.             #pragma target 5.0
12.
13.             float mod(float x, float y)
14.             {
15.                 return x - y * floor(x/y);
16.             }
17.
18.             float4 VSMain (uint id:SV_VertexID, out float2 UV : TEXCOORD1) : SV_POSITION
19.             {
20.                 float q = floor(id / 6.0);
21.                 float3 center = float3(mod(q,1000.0),0.0, floor(q/1000.0));
22.                 center *= 2..xxx;
23.                 if (mod(float(id),6)==0)
24.                 {
25.                     UV = float2(0,0);
26.                     float3 worldPos = float3(-0.5,0,-0.5) + center;
27.                     return UnityObjectToClipPos(float4(worldPos,1.0));
28.                 }
29.                 else if (mod(float(id),6)==1)
30.                 {
31.                     UV = float2(1,0);
32.                     float3 worldPos = float3(0.5,0,-0.5) + center;
33.                     return UnityObjectToClipPos(float4(worldPos,1.0));
34.                 }
35.                 else if (mod(float(id),6)==2)
36.                 {
37.                     UV = float2(0,1);
38.                     float3 worldPos = float3(-0.5,0,0.5) + center;
39.                     return UnityObjectToClipPos(float4(worldPos,1.0));
40.                 }
41.                 else if (mod(float(id),6)==3)
42.                 {
43.                     UV = float2(1,0);
44.                     float3 worldPos = float3(0.5,0,-0.5) + center;
45.                     return UnityObjectToClipPos(float4(worldPos,1.0));
46.                 }
47.                 else if (mod(float(id),6)==4)
48.                 {
49.                     UV = float2(1,1);
50.                     float3 worldPos = float3(0.5,0,0.5) + center;
51.                     return UnityObjectToClipPos(float4(worldPos,1.0));
52.                 }
53.                 else
54.                 {
55.                     UV = float2(0,1);
56.                     float3 worldPos = float3(-0.5,0,0.5) + center;
57.                     return UnityObjectToClipPos(float4(worldPos,1.0));
58.                 }
59.             }
60.
61.             float4 PSMain (float4 vertex:SV_POSITION, float2 UV : TEXCOORD1) : SV_Target
62.             {
63.                 float2 S = UV*2.0-1.0;
64.                 if (dot(S.xy, S.xy) > 1.0) discard;
65.                 return float4(0,0,1,1);
66.             }
67.             ENDCG
68.         }
69.
70.     }
71. }

VictorKs, Invertex and bgolus like this.
3. VictorKs

Joined:
Jun 2, 2013
Posts:
148
Thanks a lot for the reply I didn't know about procedural drawing. I thought about baking a quad on my characters meshes but this is much better!

4. Przemyslaw_Zaworski

Joined:
Jun 9, 2017
Posts:
188
I removed branching from vertex shader. Now, for VS, I have 24 math instructions and 2 temp registers. Previous example was related with calculating circles centers procedurally as grid of circles, now example with array:

Code (CSharp):
2. // Script renders 2048 circles with single draw call and their center coordinates
3. // are calculated once on the CPU and sent to GPU array.
4. // To use more than 2048 circles, you can use structured buffer, bake point data to texture,
5. // or just calculate circles center coordinates procedurally (directly inside vertex shader).
6. using UnityEngine;
7. public class DrawCircles : MonoBehaviour
8. {
10.     protected Material material;
11.     void Awake()
12.     {
14.         float[] bufferX = new float[2048];
15.         float[] bufferY = new float[2048];
16.         for (int i=0; i<2048; i++)
17.         {
18.             bufferX[i] = Random.Range(0.0f, 120.0f);
19.             bufferY[i] = Random.Range(0.0f, 120.0f);
20.         }
21.         material.SetFloatArray("BufferX", bufferX);
22.         material.SetFloatArray("BufferY", bufferY);
23.     }
24.     void OnRenderObject()
25.     {
26.         material.SetPass(0);
27.         Graphics.DrawProcedural(MeshTopology.Triangles, 6, 2048);
28.     }
29. }
Code (CSharp):
2. {
4.     {
5.         Pass
6.         {
7.             Cull Off
8.             CGPROGRAM
9.             #pragma vertex VSMain
10.             #pragma fragment PSMain
11.             #pragma target 5.0
12.
13.             float BufferX[2048];
14.             float BufferY[2048];
15.
16.             float mod(float x, float y)
17.             {
18.                 return x - y * floor(x/y);
19.             }
20.
21.             float3 hash(float p)
22.             {
23.                 float3 p3 = frac(p.xxx * float3(.1239, .1237, .2367));
24.                 p3 += dot(p3, p3.yzx+63.33);
25.                 return frac((p3.xxy+p3.yzz)*p3.zyx);
26.             }
27.
28.             float4 VSMain (uint id:SV_VertexID, out float2 uv:TEXCOORD0, inout uint instance:SV_INSTANCEID) : SV_POSITION
29.             {
30.                 float3 center = float3(BufferX[instance], 0.0, BufferY[instance]);
31.                 float u = mod(float(id),2.0);
32.                 float v = sign(mod(126.0,mod(float(id),6.0)+6.0));
33.                 uv = float2(u,v);
34.                 return UnityObjectToClipPos(float4(float3(sign(u)-0.5, 0.0, sign(v)-0.5) + center,1.0));
35.             }
36.
37.             float4 PSMain (float4 vertex:SV_POSITION, float2 uv:TEXCOORD0, uint instance:SV_INSTANCEID) : SV_Target
38.             {
39.                 float2 S = uv*2.0-1.0;
40.                 if (dot(S.xy, S.xy) > 1.0) discard;
41.                 return float4(hash(float(instance)), 1.0);
42.             }
43.             ENDCG
44.         }
45.     }
46. }