Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Why are Primitives so much faster than (even hardcoded) meshes?

Discussion in 'Editor & General Support' started by paulrahme, Aug 9, 2012.

  1. paulrahme

    paulrahme

    Joined:
    Apr 25, 2012
    Posts:
    17
    Hi,

    I'm holding thumbs that a member of Unity staff with under-the-hood knowledge sees this :)

    We are working on a 3D plotting application for big data (10,000 - 65,000 items, with colliders for mouse hovering).

    The plotted items can be Unity Primitives (Cube, Sphere, etc), procedurally generated shapes (Cone, Tetrahedron, etc), or imported models (for more detailed 3D shapes).

    Our procedurally generated shapes render a lot more slowly than ones created by the CreatePrimitive() command - even a 4-triangle panel replacement makes the profiler spend twice as long in MeshRenderer.Render -> Mesh.DrawVBO, compared to Unity's 200 triangle version! (The GameObject looks identical apart from the mesh).

    Does anyone know why Unity's built-in primitives are so much quicker, despite having much more detailed meshes? And more importantly, if there's a way to get our procedurally generated ones up to this speed?

    Thanks
    -Paul
     
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Your problem is the procedurally.
    Unity does not procedurally generate it, it uses the internally stored mesh which is optimized already.

    You should do your comparision with unity primitives and own meshes that you place in the project and on which you enable the mesh optimize flag to get a directly comparable result.

    That being said it also heavily depends on what you plug on them and more relevant, if you attach mesh colliders for your interaction instead of primitive ones (that wouldn't impact rendering though, that would impact the frametime on the cpu side) among other things.

    without more detailed informations its hard to give a detailed insight beside the 'general stuff'
     
    Last edited: Aug 9, 2012
  3. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    To provide a more helpful answer you can:

    - run mesh optimise on it (see docs)
    - don't generate anything you don't absolutely need
    - ensure all use sharedMaterial so they batch

    Some profiler results would be cool like draw call counts between the two types etc! good luck.
     
  4. paulrahme

    paulrahme

    Joined:
    Apr 25, 2012
    Posts:
    17
    Thanks for the answers so far! :)

    I've tested Unity Primitives vs. Imported Models vs. Procedurally Generated Meshes.

    • Unity Primitives still come out fastest in the profiler. The other 2 are similar speed to each other, but noticeably slower.
    • Imported Models have "Optimize Mesh" checked.
    • Procedurally Generated meshes do call Mesh.Optimize().
    Below is the code for a procedurally generated tetrahedron shape. The material that gets passed in is a pre-made, shared one between all the objects (we are plotting 1,000 to 65,000 of these as a 3D graph). The code for loading custom models is similar, but uses the mesh/verts/triangles from a prefab in an on-disc AssetBundle.

    Can you see anything that can use obvious improvement?

    Code (csharp):
    1.  
    2.     /*
    3.     Vertices for the tip + triangular base
    4.              0 (y=1)
    5.             /|\
    6.            / | \
    7.           /  |  \
    8.          /   |   \
    9.         3....|....2
    10.          \   |   /
    11.           \  |  /
    12.            \ | / (y=-1)
    13.             \|/
    14.              1
    15.     */
    16.  
    17.     // Vertices for corners
    18.     static Vector3 TetrahedronP0 = new Vector3(0.0f, 1.0f, 0.0f);
    19.     static Vector3 TetrahedronP1 = new Vector3(0.0f, -1.0f, 1.0f);
    20.     static Vector3 TetrahedronP2 = new Vector3(1.0f, -1.0f, -1.0f);
    21.     static Vector3 TetrahedronP3 = new Vector3(-1.0f, -1.0f, -1.0f);
    22.     static Vector3[] gTetrahedronVerts = new Vector3[] {TetrahedronP0, TetrahedronP1, TetrahedronP2, TetrahedronP3};
    23.    
    24.     // Triangle coordinates
    25.     static int[] gTetrahedronTriangles = new int[]
    26.     {
    27.         0, 3, 1,    // Front left
    28.         0, 2, 3,    // Back
    29.         0, 1, 2,    // Front right
    30.         1, 2, 3,    // Bottom
    31.     };
    32.    
    33.     // UV coordinates for texture (note: not accurate, will look weird if textured)
    34.     static Vector2[] gTetrahedronUVs = new Vector2[]
    35.     {
    36.         new Vector2(0.0f, 1.0f),
    37.         new Vector2(0.0f, 1.0f),
    38.         new Vector2(0.0f, 1.0f),
    39.         new Vector2(0.0f, 1.0f),
    40.     };
    41.  
    42.     public GameObject CreateTetrahedron(Vector3 pos, Material material)
    43.     {
    44.         GameObject gameObj = new GameObject("Tetrahedron");
    45.        
    46.         // Get/create mesh filter
    47.         MeshFilter meshFilter = gameObj.GetComponent<MeshFilter>();
    48.         if (meshFilter == null)
    49.         {
    50.             meshFilter = gameObj.AddComponent<MeshFilter>();
    51.         }
    52.        
    53.         // Get/create mesh
    54.         Mesh mesh = meshFilter.sharedMesh;
    55.         if (mesh == null)
    56.         {
    57.             meshFilter.mesh = new Mesh();
    58.             mesh = meshFilter.sharedMesh;
    59.         }
    60.        
    61.         // Get/add mesh renderer
    62.         MeshRenderer meshRenderer = gameObj.GetComponent<MeshRenderer>();
    63.         if (meshRenderer == null)
    64.         {
    65.             meshRenderer = gameObj.AddComponent<MeshRenderer>();
    66.         }
    67.        
    68.         // Set material
    69.         meshRenderer.sharedMaterial = material;
    70.         meshRenderer.castShadows = meshRenderer.receiveShadows = false;
    71.  
    72.         // Create verts  triangles
    73.         mesh.Clear();
    74.         mesh.vertices = gTetrahedronVerts;
    75.         mesh.triangles = gTetrahedronTriangles;
    76.        
    77.         // Create UVs for material's texture
    78.         mesh.uv = mesh.uv2 = gTetrahedronUVs;
    79.  
    80.         // Refresh mesh
    81.         mesh.RecalculateNormals();
    82.         mesh.RecalculateBounds();
    83.         mesh.Optimize();
    84.        
    85.         gameObj.transform.position = pos;
    86.        
    87.         return gameObj;
    88.     }
    89.  
     
  5. Kragh

    Kragh

    Joined:
    Jan 22, 2008
    Posts:
    656
    As dreamora said, Unity doesn't build the mesh for a primitive when you ask for it, it has it already build, and just instantiates it. So that will be a lot faster, obviously. Have you thought about building these meshes you need, and save them as assets, and then just instantiate them when you need them? Why build them on the fly, if they are so mathematically static, as the code implies? Calculate them once, save them as assets, instantiate when needed. Will be just as fast as the build-in primitives, I would guess.

    EDIT: Unity loads an instance of each primitive into ram at start up, and instantiates from that, and not from an asset on disc. So if you want the same speed, you would do the same :)
     
    Last edited: Sep 18, 2012
  6. paulrahme

    paulrahme

    Joined:
    Apr 25, 2012
    Posts:
    17
    Thanks for the info - will definitely try what you suggest.

    I'm not too worried about the "loading off disc / creating mathematically" speed, it's more the drawing speed that slows down when it's not a built-in primitive - even for built-in assets.

    But loading 1 of each on startup and instantiating sounds like a much more optimised plan - thanks :)