Search Unity

Help with rendering a large grid of entities

Discussion in 'Graphics for ECS' started by NanushTol, Aug 23, 2019.

  1. NanushTol

    NanushTol

    Joined:
    Jan 9, 2018
    Posts:
    131
    I'm creating a simplistic weather system, and I have got a large grid of entities (about 20,000) , each one containing several values like temperature, motion vector, water content, Co2... etc...

    I want to give each grid cell it own color according to the a specific data (like temperature) , and I understand there is no simple way of changing individual entity color yet, so I have tried this 2 ways.

    1. Rendering the data by creating a 2DTexture from each data value and applying that texture to a material held by a standard OOP Mesh Renderer, this works but it is very very slow compared to the rest of the calculations done by the weather system, for the most part of the process I cannot use parallel jobs and it creates a serious bottleneck and a heavy frame drop
    2. I have also tried using the new vfx particles, this works very nice and I can even show the direction and velocity of the wind in the weather system, which is a very nice bonus. but it's extremely slow, having 20,000 particles system is not ideal o_O.
      and for some reason the VFX & HDRP packages really slow down my system even when not running the simulation and just working in the editor.
    Are there any other ways of representing or rendering the data of the individual grid cells(Entities)?
     
  2. ndesy

    ndesy

    Joined:
    Jul 22, 2019
    Posts:
    20
    You can use Graphics.DrawMeshInstance with MaterialPropertyBlock to pass an array of colors to the shader.

    Here is a basic ECS render system to specifically have custom color per entity.

    Code (CSharp):
    1. public class RenderSystem : JobComponentSystem
    2.     {
    3.         private const int BATCH_SIZE = 1023;
    4.  
    5.         private EntityQuery _renderQuery;
    6.         private Matrix4x4[] _matrices = new Matrix4x4[BATCH_SIZE];
    7.         private Vector4[] _colors = new Vector4[BATCH_SIZE];
    8.         private MaterialPropertyBlock _propertyBlock = new MaterialPropertyBlock();
    9.         private List<RenderData> _renderDatas = new List<RenderData>(2);
    10.        
    11.  
    12.         protected override void OnCreate()
    13.         {
    14.             _renderQuery = GetEntityQuery(new EntityQueryDesc {
    15.                 All = new[]{
    16.                     ComponentType.ReadOnly<RenderData>(),
    17.                     ComponentType.ReadOnly<ParentId>(),
    18.                     ComponentType.ReadOnly<Rotation>(),
    19.                     ComponentType.ReadOnly<Position>(),
    20.                     ComponentType.ReadOnly<Scale>(),
    21.                     ComponentType.ReadOnly<RenderColor>()
    22.                 }
    23.             });
    24.         }
    25.        
    26.         protected override JobHandle OnUpdate(JobHandle inputDependencies)
    27.         {
    28.            
    29.             EntityManager.GetAllUniqueSharedComponentData(_renderDatas);
    30.  
    31.             if (_renderDatas.Count == 0)
    32.             {
    33.                 // nothing to render
    34.                 return inputDependencies;
    35.             }
    36.            
    37.             // collect parents matrices
    38.             var parentMatrices = new NativeHashMap<int, float4x4>(16, Allocator.TempJob);
    39.             var collectParents = new CollectParentMatricesJob
    40.             {
    41.                 output = parentMatrices.AsParallelWriter()
    42.             };
    43.            
    44.             collectParents.Schedule(this, inputDependencies).Complete();
    45.            
    46.             for (int renderIndex = 0; renderIndex < _renderDatas.Count; renderIndex++)
    47.             {
    48.                 RenderInstancedMesh(_renderDatas[renderIndex], parentMatrices, inputDependencies);
    49.             }
    50.  
    51.             parentMatrices.Dispose();
    52.             _renderDatas.Clear();
    53.            
    54.             return inputDependencies;
    55.         }
    56.  
    57.         private void RenderInstancedMesh(RenderData renderData, NativeHashMap<int, float4x4> parentMatrices, JobHandle jobHandle)
    58.         {
    59.             _renderQuery.SetFilter(renderData);
    60.             int count = _renderQuery.CalculateEntityCount();
    61.             if (count == 0)
    62.             {
    63.                 return;
    64.             }
    65.            
    66.             var matricesBuffer = new NativeArray<float4x4>(count, Allocator.TempJob);
    67.             var colorsBuffer = new NativeArray<Vector4>(count, Allocator.TempJob);
    68.  
    69.             CollectMatricesJob job = new CollectMatricesJob
    70.             {
    71.                 output = matricesBuffer,
    72.                 colors = colorsBuffer,
    73.                 parentMatrices = parentMatrices
    74.             };
    75.  
    76.             JobForEachExtensions.Schedule(job, _renderQuery, jobHandle).Complete();
    77.  
    78.            
    79.             for (int i = 0; i < count; i += BATCH_SIZE)
    80.             {
    81.                 int len = Math.Min(count - i, BATCH_SIZE);
    82.                
    83.                 Utils.CopyNativeToManaged(ref _matrices, matricesBuffer, i, len);
    84.                 Utils.CopyNativeToManaged<Vector4>(ref _colors, colorsBuffer, i, len);
    85.                 _propertyBlock.SetVectorArray("_Color", _colors);
    86.                 Graphics.DrawMeshInstanced(renderData.Mesh, 0, renderData.Material, _matrices, len, _propertyBlock);
    87.             }
    88.                
    89.             matricesBuffer.Dispose();
    90.             colorsBuffer.Dispose();
    91.         }
    92.      
    93.    
    94.        
    95.         #region Jobs
    96.      
    97.         [BurstCompile]
    98.         struct CollectParentMatricesJob : IJobForEach<Parent, Position, Rotation, Scale>
    99.         {
    100.             [WriteOnly] public NativeHashMap<int, float4x4>.ParallelWriter output;
    101.            
    102.             public void Execute(
    103.                 [ReadOnly] ref Parent parent,
    104.                 [ReadOnly] ref Position position,
    105.                 [ReadOnly] ref Rotation rotation,
    106.                 [ReadOnly] ref Scale scale)
    107.             {
    108.                 output.TryAdd(parent.Id, float4x4.TRS(position.Value, rotation.Value, scale.Value));
    109.             }
    110.         }
    111.        
    112.  
    113.         [BurstCompile]
    114.         struct CollectMatricesJob : IJobForEachWithEntity<ParentId, Rotation, Position, Scale, RenderColor>
    115.         {
    116.             [WriteOnly] public NativeArray<float4x4> output;
    117.  
    118.             [WriteOnly] public NativeArray<Vector4> colors;
    119.            
    120.             [ReadOnly] public NativeHashMap<int, float4x4> parentMatrices;
    121.  
    122.             public void Execute(
    123.                 Entity entity,
    124.                 int index,
    125.                 [ReadOnly] ref ParentId parent,
    126.                 [ReadOnly] ref Rotation rotation,
    127.                 [ReadOnly] ref Position position,
    128.                 [ReadOnly] ref Scale scale,
    129.                 [ReadOnly] ref RenderColor color)
    130.             {
    131.                 // it's a lot cheaper to set parentId to zero instead of removing the ParentId component
    132.                 if (parent.Value == 0)
    133.                 {
    134.                     output[index] = float4x4.TRS(position.Value, rotation.Value, scale.Value);
    135.                     colors[index] = color.Value;
    136.                 }
    137.                 else
    138.                 {
    139.                     parentMatrices.TryGetValue(parent.Value, out float4x4 parentMatrix);
    140.                     output[index] = math.mul(parentMatrix, float4x4.TRS(position.Value, rotation.Value, scale.Value));
    141.                     colors[index] = color.Value;
    142.                 }
    143.             }
    144.         }
    145.         #endregion
    146.  
    147.     }
     
    Razmot likes this.
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
  4. ndesy

    ndesy

    Joined:
    Jul 22, 2019
    Posts:
    20
    Why not? Can you elaborate?
     
  5. NanushTol

    NanushTol

    Joined:
    Jan 9, 2018
    Posts:
    131
    Cool stuff, thank you!
    ndesy this looks great! I didnt know about that.

    DreamingImLatios
    The GetRawTextureData works amazing!

    It now runs at around 35 to 45 fps, and if i could get the gaps between jobs removed or shorten, I could shave of some more ms
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    It is slower, requires breaking your draw calls into batches of 1023, and typically requires allocating GC every frame. ComputeBuffer has API to to intialized all its data using a NativeArray and you can use Graphics.DrawMeshInstancedIndirect to draw in access of 1023 instances in a single drawcall.