Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to draw many circles efficiently

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

  1. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    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

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    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. {
    5.     public Shader shader;
    6.     protected Material material;
    7.    
    8.     void Start()
    9.     {
    10.         material = new Material(shader);
    11.     }
    12.  
    13.     void OnRenderObject()
    14.     {
    15.         material.SetPass(0);
    16.         Graphics.DrawProcedural(MeshTopology.Triangles, 6 * 1000000, 1);
    17.     }
    18. }
    19.  
    Shader:

    Code (CSharp):
    1. Shader "Draw Circles"
    2. {
    3.     Subshader
    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. }
    upload_2019-10-31_22-8-49.png
     
  3. VictorKs

    VictorKs

    Joined:
    Jun 2, 2013
    Posts:
    242
    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

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    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):
    1. // Add script to camera and assign shader "DrawCircles".
    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. {
    9.     public Shader shader;
    10.     protected Material material;
    11.     void Awake()
    12.     {
    13.         material = new Material(shader);
    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):
    1. Shader "Draw Circles"
    2. {
    3.     Subshader
    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. }
    upload_2019-11-4_15-58-15.png
     
    lovewessman and addent like this.
  5. lovewessman

    lovewessman

    Joined:
    Jun 15, 2019
    Posts:
    6
    Thank you so much for sharing this awesome shader!

    Just one thing: I don't know why exactly, but in order for this to work for me I need to use 64bit precision in the shader for the line where you calculate v, so basically I have to change the mod() function to use doubles and also make the line where v is calculated into
    Code (csharp):
    1. float v = sign(mod(126.0L,mod(double(id),6.0L)+6.0L));
    . If I don't do this I only get the 2nd half of the circle. The first triangle gets calculated wrong, so either 126 mod (0 mod 6 + 6), 126 mod (1 mod 6 + 6), or 126 mod (2 mod 6 + 6) is giving me the wrong value instead of the expected 0,0,1. Hope this can help someone else in the future.
     
  6. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    327
    Yes, I discovered that previous formula can give incorrect results on some GPUs. Try to replace u,v with:

    Code (CSharp):
    1. float u = sign(mod(20.0, mod(float(id), 6.0) + 2.0));
    2. float v = sign(mod(18.0, mod(float(id), 6.0) + 2.0));
    Example with ComputeBuffer:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Runtime.InteropServices;
    3.  
    4. public class DrawCircles : MonoBehaviour
    5. {
    6.     public Shader DrawCirclesShader;
    7.  
    8.     private ComputeBuffer _ComputeBuffer;
    9.     private Material _Material;
    10.     private const int _Count = 256 * 256; // 65536
    11.  
    12.     public struct Circle
    13.     {
    14.         public Vector3 Position;
    15.         public Vector3 Color;
    16.     };
    17.  
    18.     void Awake()
    19.     {
    20.         _ComputeBuffer = new ComputeBuffer(_Count, Marshal.SizeOf(typeof(Circle)), ComputeBufferType.Default);
    21.         _Material = new Material(DrawCirclesShader);
    22.         Circle[] circles = new Circle[_Count];
    23.         for (uint i = 0; i < _Count; i++)
    24.         {
    25.             float x = Random.Range(0.0f, 512.0f);
    26.             float y = Random.Range(0.0f, 1.0f);
    27.             float z = Random.Range(0.0f, 512.0f);
    28.             float r = Random.Range(0.0f, 1.0f);
    29.             float g = Random.Range(0.0f, 1.0f);
    30.             float b = Random.Range(0.0f, 1.0f);
    31.             circles[i].Position = new Vector3(x, y, z);
    32.             circles[i].Color = new Vector3(r, g, b);
    33.         }
    34.         _ComputeBuffer.SetData(circles);
    35.         _Material.SetBuffer("_ComputeBuffer", _ComputeBuffer);
    36.     }
    37.  
    38.     void OnRenderObject()
    39.     {
    40.         _Material.SetPass(0);
    41.         Graphics.DrawProcedural(MeshTopology.Triangles, 6, _Count);
    42.     }
    43.  
    44.     void OnDestroy()
    45.     {
    46.         Destroy(_Material);
    47.         _ComputeBuffer.Release();
    48.     }
    49. }
    Code (CSharp):
    1. Shader "Draw Circles"
    2. {
    3.     Subshader
    4.     {
    5.         Pass
    6.         {
    7.             Cull Off
    8.             CGPROGRAM
    9.             #pragma vertex VSMain
    10.             #pragma fragment PSMain
    11.             #pragma target 5.0
    12.  
    13.             struct Circle
    14.             {
    15.                 float3 Position;
    16.                 float3 Color;
    17.             };
    18.  
    19.             StructuredBuffer<Circle> _ComputeBuffer;
    20.  
    21.             float Mod(float x, float y)
    22.             {
    23.                 return x - y * floor(x/y);
    24.             }
    25.  
    26.             float4 VSMain (uint id : SV_VertexID, out float2 uv : TEXCOORD0, inout uint instance : SV_INSTANCEID) : SV_POSITION
    27.             {
    28.                 float3 center = _ComputeBuffer[instance].Position;
    29.                 float u = sign(Mod(20.0, Mod(float(id), 6.0) + 2.0));
    30.                 float v = sign(Mod(18.0, Mod(float(id), 6.0) + 2.0));
    31.                 uv = float2(u,v);
    32.                 float4 position = float4(float3(sign(u) - 0.5, 0.0, sign(v) - 0.5) + center, 1.0);
    33.                 return UnityObjectToClipPos(position);
    34.             }
    35.  
    36.             float4 PSMain (float4 vertex : SV_POSITION, float2 uv : TEXCOORD0, uint instance : SV_INSTANCEID) : SV_Target
    37.             {
    38.                 float2 s = uv * 2.0 - 1.0;
    39.                 if (dot(s.xy, s.xy) > 1.0) discard;
    40.                 return float4(_ComputeBuffer[instance].Color, 1.0);
    41.             }
    42.             ENDCG
    43.         }
    44.     }
    45. }
     
    bigy and lovewessman like this.