Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Drawing 1 Million cubes!

Discussion in 'General Graphics' started by laurentlavigne, Feb 20, 2016.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,452
    So I'm chatting with a buggy of mine, building his own engine and pushes half a million cubes each frame at 100 fps, he makes fun of Unity so I build a simple test.
    First test using Graphics.DrawMesh : 100,000 cubes run at 5 fps, shadows off.
    I figure rebuilding the VBO whatever each frame takes a toll so I instantiated 100,000 cubes with no collider and woohoo 7 fps.

    So I'm wondering, is there a good way to draw tons of geometry very fast in unity - apart from DrawMeshProcedural which don't exist on mac?
     
    Last edited: Feb 20, 2016
    AzatKhafizovInno likes this.
  2. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    There's much variables here.

    Mesh drawing works best with meshes of atleast ~1k verts, preferably in the 5-20k verts range, so combine meshes where possible. There's an overhead per Graphics.DrawMesh (or any other graphics API that is) that simply makes it impossible to make 500k draw calls per frame at 100 fps. Your 'buggy' probably does this as well.

    The shader. The standard shader is relatively complex, try with an unlit shader or the equivalent of whatever your 'buggy' is using. Is he using textures at all?

    'Cubes'. Are the vertices shared per face? Do the cubes have colors / uv maps / normals / tangents? Does the shader use those?

    Anyway, with proper mesh sizes and a decent shader you can easily push, say, ~5M vertices & ~10M triangles on a mid-end gpu at 100 fps. Maybe even more, it depends on how much pixels the triangles cover (and resolution with that).
     
    deus0 and theANMATOR2b like this.
  3. ng93

    ng93

    Joined:
    Aug 31, 2012
    Posts:
    40
    I guess your using 5.3? 5.4 has DX12 and GPU instancing so should be faster.
     
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,452
    Yes I am using 5.3, we'll see if instantiation speeds this up.
    It was unshared edges, random position, and my 'buggy' was using unlit color. Using unlit or mobile/diffuse gives a much lower framerate than standard. Odd, maybe stats fps counter is beyond broken.
    I just tried doing this exercise using 9 particle systems that emit cubes on awake, I get more than double the framerate.
     
  5. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    Well, I've tested it a bit:




    That's 1 024 000 cubes with no shared vertices, so 24 576 000 vertices and 36 864 000 indices together with vertex colors. FPS is in top left; 140 for overview, from corner ish inside about 250 and from the middle of it outwards 550 fps.

    This is using cubes batched per 2000, so 500 draw calls. Batching per 1000 lowered fps from 140 to 110. Also using Graphics.DrawMesh for it, with a minimal shader (new unlit, removed texture support, removed fog support, added vertex colors).

    PC is a i5 4570 / gtx 770.
     
    deus0 and theANMATOR2b like this.
  6. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,452
    This looks like a 4k demo from the 90s!
    Drawing 10x more cubes than I, you get 10x more fps - taking out of the equation osx craptastic opengl implementation and a 4x slower graphics card, I'm still 10x too slow. Care to share your project?
    I'm curious to see how you control the batching using DrawMesh.
     
  7. ng93

    ng93

    Joined:
    Aug 31, 2012
    Posts:
    40
    Last edited: Feb 24, 2016
  8. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    @laurentlavigne

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class CubeDrawer : MonoBehaviour {
    5.     public Material cubeMaterial;
    6.  
    7.     const int DesiredCubeCount = 1000 * 1000;
    8.     const int AreaSize = 512;
    9.     const float CubeRadius = 0.5f;
    10.     const float CubesPerOne = (float)DesiredCubeCount / (AreaSize * AreaSize * AreaSize);
    11.     const float AreaPerCube = 1f / CubesPerOne;
    12.     const int CubesPerBatch = 2000;
    13.     const int BatchCount = DesiredCubeCount / CubesPerBatch;
    14.     const float AreaPerBatch = CubesPerBatch * AreaPerCube;
    15.     static readonly int BatchDimension = (int)Mathf.Pow(AreaPerBatch, 1f/3f);
    16.  
    17.     static readonly List<Vector3> batchedVertices = new List<Vector3> (24 * CubesPerBatch);
    18.     static readonly List<int> batchedTriangles = new List<int> (36 * CubesPerBatch);
    19.     static readonly List<Color32> batchedColors = new List<Color32> (24 * CubesPerBatch);
    20.  
    21.     static readonly List<MeshWrapper> batches = new List<MeshWrapper> (BatchCount);
    22.  
    23.     static readonly Vector3[] verts = {
    24.         new Vector3 (-0.5f, 0.5f, 0.5f),
    25.         new Vector3 (-0.5f, -0.5f, 0.5f),
    26.         new Vector3 (-0.5f, -0.5f, -0.5f),
    27.         new Vector3 (-0.5f, 0.5f, -0.5f),
    28.  
    29.         new Vector3 (0.5f, 0.5f, 0.5f),
    30.         new Vector3 (0.5f, -0.5f, 0.5f),
    31.         new Vector3 (0.5f, -0.5f, -0.5f),
    32.         new Vector3 (0.5f, 0.5f, -0.5f),
    33.  
    34.         new Vector3 (0.5f, -0.5f, 0.5f),
    35.         new Vector3 (-0.5f, -0.5f, 0.5f),
    36.         new Vector3 (-0.5f, -0.5f, -0.5f),
    37.         new Vector3 (0.5f, -0.5f, -0.5f),
    38.  
    39.         new Vector3 (0.5f, 0.5f, 0.5f),
    40.         new Vector3 (0.5f, 0.5f, -0.5f),
    41.         new Vector3 (-0.5f, 0.5f, -0.5f),
    42.         new Vector3 (-0.5f, 0.5f, 0.5f),
    43.  
    44.         new Vector3 (0.5f, 0.5f, -0.5f),
    45.         new Vector3 (0.5f, -0.5f, -0.5f),
    46.         new Vector3 (-0.5f, -0.5f, -0.5f),
    47.         new Vector3 (-0.5f, 0.5f, -0.5f),
    48.  
    49.         new Vector3 (0.5f, 0.5f, 0.5f),
    50.         new Vector3 (0.5f, -0.5f, 0.5f),
    51.         new Vector3 (-0.5f, -0.5f, 0.5f),
    52.         new Vector3 (-0.5f, 0.5f, 0.5f)
    53.     };
    54.  
    55.     static readonly int[] tris = {
    56.         2, 1, 0, 0, 3, 2,
    57.         4, 5, 6, 6, 7, 4,
    58.         8, 9, 10, 10, 11, 8,
    59.         12, 13, 14, 14, 15, 12,
    60.         16, 17, 18, 18, 19, 16,
    61.         22, 21, 20, 20, 23, 22
    62.     };
    63.  
    64.     struct MeshWrapper {
    65.         public Mesh mesh;
    66.         public Vector3 location;
    67.     }
    68.  
    69.     // Use this for initialization
    70.     void Start () {
    71.         int cubesMade = 0;
    72.         for (int x = 0; x < AreaSize; x += BatchDimension) {
    73.             for (int y = 0; y < AreaSize; y += BatchDimension) {
    74.                 for (int z = 0; z < AreaSize; z += BatchDimension) {
    75.                     for (int i = 0; i < CubesPerBatch; i++) {
    76.                         float localX = Random.Range (0f, BatchDimension);
    77.                         float localY = Random.Range (0f, BatchDimension);
    78.                         float localZ = Random.Range (0f, BatchDimension);
    79.                         byte colorR = (byte)((x + localX) / AreaSize * 255f);
    80.                         byte colorG = (byte)((y + localY) / AreaSize * 255f);
    81.                         byte colorB = (byte)((z + localZ) / AreaSize * 255f);
    82. //                        byte colorR = (byte)(Random.Range (0.2f, 1f)*255f);
    83. //                        byte colorG = (byte)(Random.Range (0.2f, 1f)*255f);
    84. //                        byte colorB = (byte)(Random.Range (0.2f, 1f)*255f);
    85.                         Color32 color = new Color32 (colorR, colorG, colorB, (byte)255);
    86.                         Vector3 localPosition = new Vector3 (localX, localY, localZ);
    87.                         for (int vertIndex = 0; vertIndex < verts.Length; vertIndex++) {
    88.                             batchedVertices.Add (verts [vertIndex] + localPosition);
    89.                             batchedColors.Add (color);
    90.                         }
    91.                         for (int triIndex = 0; triIndex < tris.Length; triIndex++) {
    92.                             batchedTriangles.Add (tris [triIndex] + i * verts.Length);
    93.                         }
    94.                     }
    95.                     Mesh batchedMesh = new Mesh ();
    96.                     batchedMesh.SetVertices (batchedVertices);
    97.                     batchedMesh.SetColors (batchedColors);
    98.                     batchedMesh.SetTriangles (batchedTriangles, 0);
    99.                     batchedMesh.Optimize ();
    100.                     batchedMesh.UploadMeshData (true);
    101.  
    102.                     batches.Add (new MeshWrapper {mesh = batchedMesh, location = new Vector3(x,y,z)});
    103.  
    104.                     batchedVertices.Clear ();
    105.                     batchedColors.Clear ();
    106.                     batchedTriangles.Clear ();
    107.  
    108.                     cubesMade += CubesPerBatch;
    109.                 }
    110.             }
    111.         }
    112.         Debug.LogFormat ("Made {0} cubes, with {1} verts and {2} indices", cubesMade, cubesMade * verts.Length, cubesMade * tris.Length);
    113.     }
    114.  
    115.     // Update is called once per frame
    116.     void Update () {
    117.         for (int i = 0; i < batches.Count; i++) {
    118.             MeshWrapper wrapper = batches [i];
    119.             Graphics.DrawMesh (wrapper.mesh, wrapper.location, Quaternion.identity, cubeMaterial, 0);
    120.         }
    121.     }
    122. }
    123.  
    Attach script to anything in scene, set cubeMaterial in editor to some material to use.

    Top static/consts are some variables to configure what is generated (though i just noticed that CubeRadius is unused). Cube count as # of cubes to make, areaSize as the dimensions of the area the cubes are generated in, CubesPerBatch to vary the amount of cubes per batch and some math to determine how big the area per batch is.

    Verts/tris of a unique-edge-cube below it. (Had it laying around somewhere :))

    struct MeshWrapper is used to store the base location with each mesh in the batches list.

    Then loop over all the 'batch areas' with previously calculated dimensions, and generate # of cubes per batch at random positions within the batch area by pushing their verts into the batches vert list, adding the position of the cube relative to the batch root.

    Code (CSharp):
    1. Shader "Unlit/CubeShader"
    2. {
    3.     SubShader
    4.     {
    5.         Tags { "RenderType"="Opaque" }
    6.         LOD 100
    7.  
    8.         Pass
    9.         {
    10.             CGPROGRAM
    11.             #pragma vertex vert
    12.             #pragma fragment frag
    13.          
    14.             #include "UnityCG.cginc"
    15.  
    16.             struct appdata
    17.             {
    18.                 float4 vertex : POSITION;
    19.                 fixed4 color : COLOR;
    20.             };
    21.  
    22.             struct v2f
    23.             {
    24.                 float4 vertex : SV_POSITION;
    25.                 fixed4 color : COLOR;
    26.             };
    27.  
    28.             v2f vert (appdata v)
    29.             {
    30.                 v2f o;
    31.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    32.                 o.color = v.color;
    33.                 return o;
    34.             }
    35.          
    36.             fixed4 frag (v2f i) : SV_Target
    37.             {
    38.                 return i.color;
    39.             }
    40.             ENDCG
    41.         }
    42.     }
    43. }
    44.  

    CubeShader is... well, minimal, not that interesting. Doesn't even 'support' linear mode.

    For the rest there was some mouselook/fly code I copied over from my commercial project, can't share it.

    Generating all those cubes and batches takes about 1 sec on my pc (or feels like 1 sec atleast). It could be sped up by using an array instead of List<Vector3> etc, as the max size is known (cubesPerBatch * 24) and caching some values would help. But generation time wasn't an issue so yeah.
     
  9. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,452
    Thanks @Zuntatos, that's what I thought: you made your own batcher so each time you update these cubes - ouch.
    It will be interesting to see how performances change with 5.4 instancing.