Search Unity

Can this algorithm be done with the Jobs System?

Discussion in 'Entity Component System' started by konstantin_lozev, Apr 26, 2019.

  1. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    That's what I will definitely do now. I think that will be enough. Thanks a million for your help! I learned a lot about the job system. I hope others will learn too when reading this long post.
     
    DreamingImLatios likes this.
  2. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Build Node Grid and Links 00:00:00.0805724
    Instantiate Node Grid and Links 00:00:00.0365286

    Monobehaviour + Mathermatics Attempt

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System;
    4. using System.Diagnostics;
    5. using UnityEngine;
    6. using Unity.Mathematics;
    7.  
    8.  
    9. public class BuildPointCloud : MonoBehaviour
    10. {
    11.  
    12.     public GameObject prefabNode;
    13.     public GameObject prefabLink;
    14.  
    15.     void Start()
    16.     {
    17.         BuildGrid();  
    18.     }
    19.  
    20.     public int gridSize = 20;
    21.  
    22.     public struct Node
    23.     {
    24.         public int index;
    25.         public float3 pos;
    26.         public int[] links;
    27.         public int linkCount;
    28.  
    29.         public Node(int i, float3 p)
    30.         {
    31.             linkCount = 0;
    32.             index = i;
    33.             pos = p;
    34.             links = new int[4]; // limits the number of node links
    35.         }
    36.  
    37.         public bool Add(int link)
    38.         {
    39.             if (linkCount < links.Length)
    40.             {
    41.                 links[linkCount++] = link;
    42.                 return true;
    43.             }
    44.             else
    45.             {
    46.                 for (int i = 0; i < linkCount; i++)
    47.                 {
    48.                     if (linkList[links[i]].distance > linkList[link].distance)
    49.                     {
    50.                         links[i] = link;
    51.                         return true;
    52.                     }
    53.                 }
    54.             }
    55.             return false;
    56.         }
    57.     }
    58.  
    59.     public struct NodeLink
    60.     {
    61.         public int nodeA;
    62.         public int nodeB;
    63.         public float3 link;
    64.         public float distance;
    65.  
    66.         public NodeLink(int a, int b, float3 d)
    67.         {
    68.             nodeA = a;
    69.             nodeB = b;
    70.             link = d;
    71.             distance = math.distance(float3.zero, d);
    72.         }
    73.     }
    74.  
    75.  
    76.     public float nscale = 0.01f;
    77.     public float scale = 2f;
    78.  
    79.     public static NodeLink[] linkList = new NodeLink[10000];
    80.  
    81.     void BuildGrid()
    82.     {
    83.         Stopwatch stopwatchA = new Stopwatch();
    84.         stopwatchA.Start();
    85.  
    86.         Node[] nodeList = new Node[2000];
    87.        
    88.  
    89.         float posx;
    90.         float posy;
    91.         float posz;      
    92.  
    93.         float3 pos;
    94.  
    95.         float halfGridSize = gridSize * 0.5f;
    96.  
    97.         float radius;
    98.  
    99.         float gridScale = 1f / gridSize;
    100.  
    101.         float n;
    102.  
    103.         int count = 0;
    104.  
    105.         float3 offset = UnityEngine.Random.insideUnitSphere * 10f;
    106.  
    107.         float3 randomPoint;
    108.         float3 centre = new float3(0f, 0f, 0f);
    109.        
    110.  
    111.         for (int x = 0; x < gridSize; x++)
    112.         {
    113.             posx = x;
    114.             pos.x = (posx - halfGridSize) * gridScale;
    115.  
    116.             for (int y = 0; y < gridSize; y++)
    117.             {
    118.                 posy = y;
    119.                 pos.y = (posy - halfGridSize) * gridScale;
    120.  
    121.                 for (int z = 0; z < gridSize; z++)
    122.                 {
    123.                     posz = z;                  
    124.                    
    125.                     pos.z = (posz - halfGridSize) * gridScale;
    126.  
    127.                     radius = math.distance(centre,pos);                  
    128.  
    129.                     n = (noise.snoise(pos * nscale + offset)+0.45f)*0.2f + 0.2f;
    130.                    
    131.  
    132.                     //Debug.Log(pos.ToString() + " m:" + radius + " n:" + n);
    133.  
    134.                     if ( n > radius)
    135.                     {
    136.                         randomPoint = UnityEngine.Random.insideUnitSphere;
    137.                         randomPoint *= 0.2f;
    138.  
    139.                         nodeList[count] = new Node(count, (pos + randomPoint) * gridSize);
    140.  
    141.                         //nodeList[count] = new Node(count, (pos * scale + randomPoint) * gridSize);
    142.  
    143.                         count++;
    144.                     }
    145.                 }
    146.             }
    147.         }
    148.  
    149.         Node nodeA;
    150.         Node nodeB;
    151.         NodeLink link;
    152.  
    153.         int linkCount = 0;
    154.  
    155.         bool linkAfull = false;
    156.         bool linkBfull = false;
    157.  
    158.         float3 posA;
    159.         float3 posB;
    160.  
    161.         float3 linkAB;
    162.  
    163.         for (int a = 0; a < count; a++)
    164.         {
    165.             nodeA = nodeList[a];
    166.             posA = nodeA.pos;
    167.  
    168.             linkAfull = false;
    169.  
    170.             for (int b = a+1; b < count; b++)
    171.             {
    172.                 nodeB = nodeList[b];
    173.  
    174.                 posB = nodeB.pos;
    175.  
    176.                 linkBfull = false;
    177.  
    178.                 linkAB = posA - posB;
    179.  
    180.                 float d = math.distancesq(posA, posB);
    181.              
    182.                 if (d < 20f)
    183.                 {
    184.                     //Debug.Log("distance " + d);
    185.  
    186.                     link = new NodeLink(a, b, linkAB);
    187.  
    188.                     linkList[linkCount] = link;
    189.                     linkAfull = !nodeA.Add(linkCount);
    190.                     linkBfull = true;
    191.  
    192.                     if (!linkAfull)
    193.                     {
    194.                         linkBfull = !nodeB.Add(linkCount);
    195.                     }
    196.  
    197.                     if (!linkBfull) linkCount++;      
    198.                 }
    199.  
    200.                 nodeList[b] = nodeB;
    201.  
    202.                 if (linkBfull) break;
    203.             }
    204.             nodeList[a] = nodeA;
    205.         }
    206.  
    207.         stopwatchA.Stop();
    208.  
    209.         TimeSpan ts = stopwatchA.Elapsed;
    210.  
    211.         UnityEngine.Debug.Log("Build Node Grid and Links " + ts.ToString());
    212.  
    213.         UnityEngine.Debug.Log("Node Count:" + count + " Link Count:"+ linkCount );
    214.  
    215.         Transform t;
    216.  
    217.         stopwatchA.Restart();
    218.  
    219.         foreach (Node node in nodeList)
    220.         {
    221.             linkCount = 0;          
    222.  
    223.             if (node.linkCount > 0) //excludes loner nodes from final grid
    224.             {
    225.                 Instantiate<GameObject>(prefabNode, node.pos, Quaternion.identity);
    226.  
    227.                 for (int i = 0; i < node.linkCount; i++)
    228.                 {
    229.                     link = linkList[node.links[i]];
    230.  
    231.                     if (node.index == link.nodeB) // only draw links that start at this node so we scale in the right direction
    232.                     {
    233.                         linkCount++;                      
    234.  
    235.                         t = Instantiate(prefabLink, node.pos, Quaternion.LookRotation(link.link)).transform;
    236.                         t.localScale = new Vector3(1f, 1f, link.distance * 35f);
    237.                     }
    238.                 }
    239.             }
    240.         }
    241.         stopwatchA.Stop();
    242.  
    243.         ts = stopwatchA.Elapsed;
    244.  
    245.         UnityEngine.Debug.Log("Instanciate Node Grid and Links " + ts.ToString());
    246.     }  
    247. }
    248.  
    Produces grid networks like this using a 3d starting grid that is limited to a sphere with a 3D noise function to vary the radius then the grid points are distorted and finally linked.



    PROS: Is superfast uses small structs and Mathematics so should be DOTS Job and Burst friendly. Removes the need for random growth and depending on noise function for exterior and interior grid point exclusion can create any type of organic shape you want (in theory).

    CONS: May need a pruning/growth pass to match required number of nodes and move outlying loner nodes back into the point cloud.

    Hope this helps, my point here is sometimes it's not optimising the existing algorithm that is needed but optimising the approach taken.
     
  3. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Hi, I did implement the Poisson Disc Distribution. It's a really simple, but clever algorithm and is very easily extendable to 3D. I had to tweak it a bit to not take the spawnpoints at random, but to always take the spawnpoint at index 0, since that resulted in somewhat better-looking medium-sized grids.
    That algorithm is hands down superior to mine.
     
    Last edited: May 4, 2019
    Singtaa likes this.
  4. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Thanks, will have a look. I actually tried the Poisson Disc Sampling someone linked above (easily extendable to 3d) and it's really efficient and produces a very even grid, unlike my original algorithm. But in any event, I am really happy that I did the work on implementing my algorithm with the job system and burst. I learned a lot!
     
    Last edited: May 4, 2019
    Singtaa likes this.
  5. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    I'm glad that it helped! I'm just using it to do fast scatter of trees and rocks in my game. But really cool to see it helping your use case too.

    I'm not sure if you already do, but you can definitely get some extra performance boost by implementing Poisson Disc Distribution with job+burst+mathematics.
     
  6. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    The script is cirrently all MonoBehaviour, but I plan to put in practice all I learned in this thread to convert it to job+burst+mathematics.
     
    Singtaa likes this.
  7. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I've had a go at Jobifying and DOTSifying my previous attempt...

    Build Node Grid and Links 00:00:00.0397612
    Instanciate Node Grid and Links 00:00:00.1296954

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System;
    4. using System.Diagnostics;
    5. using UnityEngine;
    6. using Unity.Mathematics;
    7. using Unity.Jobs;
    8. using Unity.Collections;
    9. using Unity.Burst;
    10. using Unity.Entities;
    11. using Unity.Transforms;
    12.  
    13. using Rnd = Unity.Mathematics.Random;
    14. using uRnd = UnityEngine.Random;
    15. using uDebug = UnityEngine.Debug;
    16.  
    17. public class BuildPointCloudBurst : MonoBehaviour
    18. {
    19.     public int gridSize = 20;
    20.  
    21.     public GameObject prefabNode;
    22.     public GameObject prefabLink;  
    23.  
    24.     GridContainsPointJob gridContainsPointJob;
    25.     JobHandle gridPointJobHandle;
    26.  
    27.     NativeArray<float3> points;
    28.     NativeQueue<Link> nodeLinksQ;
    29.  
    30.     FindLinksToPointJob findLinksToPointJob;
    31.     JobHandle findLinksJobHandle;
    32.  
    33.     protected void Start()
    34.     {
    35.         Stopwatch stopwatchA = new Stopwatch();
    36.         stopwatchA.Start();
    37.  
    38.         points = new NativeArray<float3>(8000, Allocator.Persistent);
    39.  
    40.         gridContainsPointJob = new GridContainsPointJob()
    41.         {
    42.             points = points,
    43.             random = new Rnd((uint)uRnd.Range(1, 100000))
    44.         };
    45.  
    46.         gridPointJobHandle = gridContainsPointJob.Schedule(8000, 32);
    47.  
    48.         nodeLinksQ = new NativeQueue<Link>(Allocator.Persistent);
    49.  
    50.         findLinksToPointJob = new FindLinksToPointJob()
    51.         {
    52.             points = points,
    53.             nodeLinksQ = nodeLinksQ.ToConcurrent()
    54.         };
    55.  
    56.         findLinksJobHandle = findLinksToPointJob.Schedule(8000, 32, gridPointJobHandle);
    57.  
    58.         gridPointJobHandle.Complete();
    59.         findLinksJobHandle.Complete();
    60.  
    61.         stopwatchA.Stop();
    62.  
    63.         TimeSpan ts = stopwatchA.Elapsed;
    64.  
    65.         UnityEngine.Debug.Log("Build Node Grid and Links " + ts.ToString());
    66.  
    67.         int count = 0;
    68.         int counter = 0;
    69.  
    70.         foreach(float3 point in points)
    71.         {          
    72.             counter++;
    73.             if (point.x != 0f || point.y != 0f || point.z != 0f)
    74.             {
    75.                 //uDebug.Log(counter + " " + point.ToString());
    76.                 count++;
    77.             }
    78.         }
    79.  
    80.         uDebug.Log("Points in range " + count);
    81.         uDebug.Log("Links Generated " + nodeLinksQ.Count);
    82.  
    83.         Transform t;
    84.  
    85.         stopwatchA.Restart();
    86.  
    87.         float scale = 5f; // the grid fits into a 1f space so scale to fit prefabs
    88.  
    89.         Entity nodePrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefabNode, World.Active);
    90.         var entityManger = World.Active.EntityManager;
    91.  
    92.         foreach (float3 node in points)
    93.         {
    94.             var instance = entityManger.Instantiate(nodePrefab);
    95.             var position = transform.TransformPoint(node * scale);
    96.             entityManger.SetComponentData(instance, new Translation { Value = position });          
    97.         }
    98.  
    99.         Link link;
    100.         float3 nodeA;
    101.         float3 nodeB;
    102.         float3 direction;
    103.         float distance;
    104.  
    105.         Entity linkPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefabLink, World.Active);
    106.  
    107.  
    108.         do
    109.         {
    110.             link = nodeLinksQ.Dequeue();
    111.  
    112.             nodeA = points[link.pointA];
    113.             nodeB = points[link.pointB];
    114.  
    115.             direction = nodeB - nodeA;
    116.  
    117.             distance = math.distance(nodeA, nodeB);
    118.  
    119.             var instance = entityManger.Instantiate(linkPrefab);
    120.             var position = transform.TransformPoint(nodeA * scale);
    121.  
    122.             entityManger.SetComponentData(instance, new Translation { Value = position });
    123.  
    124.             //t = Instantiate(prefabLink, nodeA * scale, Quaternion.LookRotation(direction)).transform;
    125.             Quaternion lookAt = Quaternion.LookRotation(direction);
    126.             entityManger.SetComponentData(instance, new Rotation { Value = lookAt });
    127.  
    128.             float3 scalelink = new Vector3(1f, 1f, distance * scale * 35f);
    129.             entityManger.SetComponentData(instance, new NonUniformScale { Value = scalelink });
    130.         }
    131.         while (nodeLinksQ.Count > 0);
    132.  
    133.                      
    134.         stopwatchA.Stop();
    135.  
    136.         ts = stopwatchA.Elapsed;
    137.  
    138.         UnityEngine.Debug.Log("Instanciate Node Grid and Links " + ts.ToString());
    139.     }
    140.  
    141.     private void OnDestroy()
    142.     {
    143.         points.Dispose();
    144.         nodeLinksQ.Dispose();
    145.     }
    146.  
    147.  
    148.     [BurstCompile]
    149.     struct GridContainsPointJob : IJobParallelFor
    150.     {
    151.         public Rnd random;
    152.         public NativeArray<float3> points;
    153.         //public float offsetScale;
    154.         //public float3 noiseOffset;
    155.  
    156.         public void Execute(int i)
    157.         {
    158.             float x = ((i % 20f) * 0.1f) - 1f;
    159.             float y = ((i * 0.05f) % 20) * 0.1f - 1f;
    160.             float z = ((i * 0.0025f) * 0.1f) - 1f;
    161.  
    162.             float3 point = new float3(x, y, z); //points[i];
    163.  
    164.             point += random.NextFloat3() * 0.04f;
    165.  
    166.             float distance = math.distance(float3.zero, point);
    167.  
    168.             if (distance > (0.5f + noise.snoise(point)* 0.5f))
    169.             {
    170.                 point = float3.zero;
    171.                 //points[i] = float3.zero;
    172.             }
    173.             points[i] = point;
    174.         }
    175.     }  
    176.  
    177.  
    178.     public struct Link
    179.     {
    180.         public int pointA;
    181.         public int pointB;
    182.  
    183.         public Link(int a, int b)
    184.         {
    185.             pointA = a;
    186.             pointB = b;
    187.         }
    188.     }
    189.  
    190.  
    191.  
    192.     [BurstCompile]
    193.     struct FindLinksToPointJob : IJobParallelFor
    194.     {
    195.         [ReadOnly]
    196.         public NativeArray<float3> points;
    197.  
    198.         [WriteOnly]
    199.         public NativeQueue<Link>.Concurrent nodeLinksQ;
    200.  
    201.         public void Execute(int i)
    202.         {
    203.             float3 point = points[i];
    204.  
    205.             if (point.x != 0f && point.y != 0f && point.z != 0f)
    206.             {
    207.  
    208.                 int xi = i % 20;
    209.                 int yi = (i / 20) % 20;
    210.                 int zi = i / 400;
    211.  
    212.                 int minx = xi - 3;
    213.                 int miny = yi - 3;
    214.                 int minz = zi - 3;
    215.  
    216.                 if (minx < 0) minx = 0;
    217.                 if (miny < 0) miny = 0;
    218.                 if (minz < 0) minz = 0;
    219.  
    220.                 int maxx = xi + 3;
    221.                 int maxy = yi + 3;
    222.                 int maxz = zi + 3;
    223.  
    224.                 if (maxx > 20) maxx = 20;
    225.                 if (maxy > 20) maxy = 20;
    226.                 if (maxz > 20) maxz = 20;
    227.  
    228.                 int x = minx;
    229.                 int y = miny;
    230.                 int z = minz;
    231.  
    232.                 int linkTo;
    233.  
    234.                 float3 otherPoint;
    235.  
    236.                 do
    237.                 {
    238.                     linkTo = x + y * 20 + z * 400;
    239.  
    240.                     if (linkTo != i)
    241.                     {
    242.                         otherPoint = points[linkTo];
    243.  
    244.                         if (otherPoint.x != 0f && otherPoint.y != 0f && otherPoint.z != 0f)
    245.                         {
    246.  
    247.                             if (math.distance(point, otherPoint) < 0.11f)
    248.                             {
    249.                                 Link link = new Link(i, linkTo);
    250.                                 nodeLinksQ.Enqueue(link);
    251.                             }
    252.                         }
    253.                     }
    254.  
    255.                     x++;
    256.  
    257.                     if (x > maxx)
    258.                     {
    259.                         x = minx;
    260.                         y++;
    261.  
    262.                         if (y > maxy)
    263.                         {
    264.                             y = miny;
    265.                             z++;
    266.                         }
    267.                     }
    268.                  
    269.  
    270.                 } while (z < maxz);
    271.             }
    272.         }
    273.     }
    274. }
    275.  
    Note the building of the nodes and links is not done within a Job, can entities that need to render be created by Jobs as that should really speed up the building process?

    Also which of the Mathermatics noise functions is the most performat when Burst compiled?
     
  8. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Thanks, I had a look, but I am afraid I don't understand the code very well. I don't like implementing in my game something that I don't fully understand, but at the same time I am intrigued to understand it.
    I can only make some superficial comments and ask some questions:
    • You seem to use the ECS for instantiating the nodes. I currently use batched instanced rendering for that, since it allows me to change their appearance with MaterialPropertyBlock. I understood from this https://forum.unity.com/threads/materialpropertyblock-support-in-meshinstancerenderersystem.562369/ that this is not yet implemented in ECS. The real part that slows down the creation is the instantiation of the sphereColliders. I saw that the newest version of ECS supports colliders and raycasts, but I have not investigated that yet.
    • I see that you use the native containers with Allocator.persistent. I understood that it's best to dispose of the native container at the end of the job completion... Of course, then you would not need to transfer all the data to the Unity arrays, but I don't know which is really advisable
    • The concept of NativeQueue is very new to me, I have not researched that at all. To that end I don't understand what the link = nodeLinksQ.Dequeue(); is meant to do.
    • I don't understand why you call .Complete() on both gridPointJobHandle.Complete(); and on findLinksJobHandle.Complete(); while the findLinksJobHandle was scheduled with a dependancy. I thought that you only need to call .Complete() on the second job...
    • I particularly don't understand how the struct GridContainsPointJob is working. I don't understand why the starting position of each point is
    Code (CSharp):
    1. float x = ((i % 20f) * 0.1f) - 1f;
    2. float y = ((i * 0.05f) % 20) * 0.1f - 1f;
    3. float z = ((i * 0.0025f) * 0.1f) - 1f;
    • I see that after assigning that initial point, you randomise its position slightly off its original position. I see that then you compare the distance of that point to the center with some value that contains a noise element. I don't understand why that comparison is done or why the particular value to compare with was chosen.
    • I don't see a check of minimum distance between the created points, which is very important to the gameplay. The Poisson Disc Sampling algorithms works excellently in that respect.
    • I also don't understand how the struct FindLinksToPointJob works either. I don't understand this part:
    Code (CSharp):
    1. int xi = i % 20;
    2. int yi = (i / 20) % 20;
    3. int zi = i / 400;
    • and what the ensuing minx, miny, minz and maxx, maxy, maxz are for
    • I think you also don't check if there has already been a link established between the two nodes
    • You also don't check if a link passes through another node on its way to the connecting node
    To be honest, I am quite happy with the Poisson Disc Sampling algorithm, especially since it creates no overlaps or congestions. I even had some fun giving that algorithm a bias and it draws some beautiful random shapes of constellations.
     
  9. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I'm new to Burst/Jobs so it's just my attempt at converting my previous Mono version.

    It just builds a 20x20x20 grid (8000 points) then excludes points outside of a radius + noise calculation. This also ensures that all nodes are within a given range so no need to do all that random node generation and range testing or Poisson Disk Sampling the distrance is build into the grid.

    Jobs NativeArrays cannot be multidimensional so if you flatten the grid you can convert it back to xyz using some division and modulus mathermatics e.g. float x = ((i % 20f) * 0.1f) - 1f;

    NativeQueues allow the linking code to add new links to nodes that are within range of the current node. Not sure if they are the best way to build/add new data but they work in this case. It's a Queue (First In First Out) so you can add new elements with Enqueue() and remove them with Dequeue().

    The min max ranges ensure that you only do a link range test against neighbouring nodes (+-3 offset) and not every other node in the grid.

    Maybe I don't need to Call complete on the first job, however a lot of the code and 'design' has evolved due to me hitting lots of errors as I converted from Mono to Jobs.
     
  10. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    1. Good point I should add this, not sure how to do it with jobs and having to use NativeQueue to make links.
    2. It is very unlikley due to the grid nature of the points and the range limit on links, however a point distance from line test could be added.
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,267
    So because Unity's ECS renderer doesn't support Material Property Blocks, you built your own renderer on the Graphics API which is independent of Game Objects, and you are not using ECS?

    One of the main selling points of ECS is that if Unity's systems don't meet your use case, you can just swap them out with your own. ECS provides so many other benefits that make it way easier to work with data in jobs and not worry about dependencies as complexity grows.

    If you're brave enough to dive into shaders, you can get some really good speedups by packing your instancing data into compute buffers or or textures which you can fill from jobs. Texture2D even has NativeArray API for this. https://docs.unity3d.com/ScriptReference/Texture2D.GetRawTextureData.html
    Granted, I don't remember if Oculus Go supports compute shaders, but iirc having instancing usually means the hardware is capable of such. I've never heard of an OpenGL ES 3.0 capable device not being able to be OpenGL ES 3.1 capable due to a hardware issue (it is always a driver-level issue).
     
  12. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    No, I am much less experienced than that :) I use this https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html. It does not need GameObjects. I simply change the MaterialPropertyBlock index in the batched arrays for the node that I want to change the look of. I only tweaked the shader with some help from the foum's shader gurus to support vertices collapsing when the passed alpha of the albedo is 0, enabling the turning on/off of the drawing of a particular node.
    Tha's really much beyond my level of knowledge ATM, to be honest.
    As I mentioned, what I am still strugging with is the need for sphereColliders for me to select the nodes. I can't have any interactivity otherwise. I had some initial thought of simple intersection detection that should work for a pointing raycast that I shared with you here https://forum.unity.com/threads/can-this-algorithm-be-done-with-the-jobs-system.668551/#post-4494196, but it is only a preliminary thinking at this stage. Or, I should delve into the new physics examples.
    BTW, I applied all you helped me learn into transitioning my approach for larger grids towards a Poisson Disc Sampling algorithm. I managed to make it work with jobs and burst without any problems. For smaller grids I still stick to my initial approach, since the results that I am getting are more varied and branching out. (the PDS algorithm gives too similar results, which is a bit boring for smaller grids) Thanks again for helping :)
     
    Last edited: May 7, 2019
  13. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Thanks, Now I have a better idea how it's meant to work. I put the float3 formulae in a C# compiler for 0<i<20 and I got this
    Code (CSharp):
    1. x=-1  y=-1   z=-1
    2. x=-0.9  y=-0.995   z=-0.99975
    3. x=-0.8  y=-0.99   z=-0.9995
    4. x=-0.7  y=-0.985   z=-0.99925
    5. x=-0.6  y=-0.98   z=-0.999
    6. x=-0.5  y=-0.975   z=-0.99875
    7. x=-0.4  y=-0.97   z=-0.9985
    8. x=-0.3  y=-0.965   z=-0.99825
    9. x=-0.2  y=-0.96   z=-0.998
    10. x=-0.09999999  y=-0.955   z=-0.99775
    11. x=1.490116E-08  y=-0.95   z=-0.9975
    12. x=0.1  y=-0.945   z=-0.99725
    13. x=0.2  y=-0.94   z=-0.997
    14. x=0.3  y=-0.935   z=-0.99675
    15. x=0.4  y=-0.93   z=-0.9965
    16. x=0.5  y=-0.925   z=-0.99625
    17. x=0.6  y=-0.92   z=-0.996
    18. x=0.7  y=-0.915   z=-0.99575
    19. x=0.8  y=-0.91   z=-0.9955
    20. x=0.9  y=-0.905   z=-0.99525
    I see that the step between the nodes' initial positions is 0.1, but should y and z not be constant within a row?
    I also have another concern with regard to the algorithm: how do you find the balance for the random offset? It appears to me that if the random offset is too small, the resulting grid would look to regular and repeating. However, if the random offset is too big, there is higher risk that some fo the nodes would be closer to each other, which is also not desirable and is handled quite well with Poisson DIsc Sampling.
     
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,267
    Exactly! This is almost identical to what you would need to do in ECS. Each node entity could have an InstancingData IComponentData which holds an integer index to a MaterialPropertyBlock array. Then all you would do is query for all entities that have a RenderMesh and an InstancingData and fill up your batches. And then you just remove all the ComponentSystems and JobComponentSystems using the Unity.Rendering.Hybrid namespace.

    I don't know how far along you actually are in development, but if it is still early on, I encourage you to try ECS. Neither sphere colliders not MaterialPropertyBlocks should stop you since you have already shown competency in being able to write custom solutions for these on this thread. ECS is going to give you a much more powerful framework to write data-oriented code (you already seem to already have a preference towards the data-oriented mindset since you are stuffing all your data into arrays anyways) as well as a little bit better authoring framework.
     
  15. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    All of this is a hobby and a way for me to learn (I like learning by doing), so in general I am open to going all in with ECS. I think I will start with the physics system to solve first my issues with the sphereColliders and get rid of all the GameObjects.
    In general, I think ECS does need badly this type of basic physics support (colliders and raycasting) for any type of interactivity. I am happy they are putting their efforts in that direction.
    On using ECS for drawing the nodes and links, I agree it is more flexible (at least from what I saw so far).
    I am not sure whether any type of custom LOD solution would make sense with my current approach with batched instanced rendering, since I actually cannot disable the rendering of individual nodes in the batch. I think the vert collapse "disappearing" in the shader is still taking up resources and for 2 LOD groups I would need 2 groups of batched arrays.
    In addition, I have a billboardong script that, if you are spinning around the grid, orients all open nodes (not the shereColliders, but the Matrix4x4 of the batched nodes) to the camera each frame. It is currently all in Mono and no jobs or burst, so there is definitely some room for using jobs and burst for that. I think only using jobs and no ECS and filling in the batched arrays back and forth with the NativeArrays each frame won't be an optimal solution.
    So yeah, I can definitely see the limitations with sticking to batched instancing only...
     
  16. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,267
    I'm not saying ECS will make rendering better with your custom solution. I'm saying that your solution can be easily tweaked to fit the mold of ECS so that the rest of your game can heavily benefit from ECS. If you don't need Unity's LOD solution, then don't even think about it.

    There is the BatchRendererGroup API if you are looking to improve performance with culling, though I don't think it fits your need as well for per-instance material properties. With ECS, you'd only be filling C# arrays from NativeArrays.
     
  17. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    To be honest, I was watching quite a few demos of ECS and they all underlined the ability of ECS to render many instances of the same entity. I am not aware of other potential benefits. That's why I was a bit sceptical of thr added benefits compared to batched instanved rendering, which does exactly that - render many instanves of the same mesh.
    I would actually need some sort of LOD solution, since for 1000 nodes I get 300.000 vertices, while the Oculus Go's target is 100.000 vertices https://developer.oculus.com/documentation/unity/latest/concepts/unity-integration-perf/
    But first things first - first on my agenda is getting rid of the sphereColliders and I need to spend some time with the physics examples (I don't see too much documentation beyond the examples yet)
     
  18. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Code (CSharp):
    1. float x = (i % 20f);
    2.             float y = ((i - x) * 0.05f) % 20f;
    3.             float z = ((i - x - y * 20f) * 0.0025f);
    4.  
    5.             x = x * 0.1f - 1f;
    6.             y = y * 0.1f - 1f;
    7.             z = z * 0.1f - 1f;
    Should be good to about 5 decimal places.
     
  19. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,267
    Other benefits include:
    • data laid out tightly in memory
    • data in a format ready to be used in jobs with burst without copying data back and forth
    • fast instantiation and destruction
    • authoring workflows
    • a physics solution
    • ability to batch-reason about any aspect of your game
    • automatic job dependency management
    • good execution order control
    And those are just the one's off the top of my head. There's many more.

    And also, Unity's ECS implementation is also using batched instanced rendering. But they use jobs for culling and LOD and completely bypass copying data to C# arrays. However, they don't support MaterialPropertyBlocks, probably because the SRP Batcher doesn't support them either. But that's not to say that your solution can't support them and still work with entities. Having entities will make it way easier for you to do culling and LOD as well as pretty much everything else in your game in a much more efficient way. It's not just about performance, it is about performance by default, meaning that as you build your game you don't have to do the crazy copying gymnastics you did in this thread to optimize an algorithm.
     
  20. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    If I was to Entitize my approach above what would be the most performant way to do that, could I:
    • Generate a set of node entities given a range.
    • Have created nodes then be processed to add link entities.
    • Instantiate the nodes by adding rendering components.
    • Instantiate the links by adding rendering components.
    For maximum throughput the linking job would only be allowed to run on nodes that have surrounding nodes created already but as soon as a node exists with a full set of neighbors it should be worked on. How could this be achieved?

    In addition are there more performant noise functions that work better with batches of tests, as it looks to me that the existing Simplex and Perlin functions do a lot of work for one result when a lot of that work is probably repeated every call?

    Also I understand that rendering components need to be command buffered so they can be applied by the main thread is this still the case?
     
  21. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Thanks for the summary :) Do you know whether there is a chance in the future for the ECS renderer to support MaterialPropertyBlock out of the box?
     
  22. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Yes this is being worked on.
     
    Covfefeh, FROS7, GilCat and 2 others like this.
  23. konstantin_lozev

    konstantin_lozev

    Joined:
    May 8, 2015
    Posts:
    99
    Cool :) I will then maybe work on other aspects and will wait for it to arrive.