Search Unity

Manual culling of MeshInstanceRenderer based on user input

Discussion in 'Graphics for ECS' started by Mersey-Viking, Feb 19, 2019.

  1. Mersey-Viking

    Mersey-Viking

    Joined:
    Nov 3, 2012
    Posts:
    4
    I have a voxel-like grid of cells, where each "on" cell is rendered as a simple cube using a MeshInstanceRenderer. There are several levels of voxels, each more detailed than the last (based on an octree structure, so the levels don't have to get very deep before the number of voxels reaches the 100k sort of levels typical for this application). Imagine it like a LOD type structure, but instead of choosing which level to render based on screen-space size, the level currently rendered is based on user input. How would I go about achieving this?

    Things I have tried:
    Writing my own instanced mesh rendering system - this works, but is very slow. It was a pretty naieve system, and I don't doubt for a minute that it was my poor understanding that contributed to the slowness.

    Adding a shared MeshInstanceRenderer to each level of voxels. Figuring this would get some nice chunking going on. But I can't figure out how to tell the MeshInstanceRendererSystem to not render a given chunk.

    Adding a Disabled tag to my archetype. This seems to bypass my system entirely, so toggling the tag doesn't happen.

    As I say, it is mostly my ignorance I think, rather than anything fundamental. My google-fu tuens up a number of posts that almost but not quite fulfil my requirements, which leads me to think I'm going about this the wrong way.
     
  2. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    64
    I'm curious, why do you say the Disabled tag bypasses your system? Why can't you include such a component in your component group query, or a require component tag attribute?
     
  3. Mersey-Viking

    Mersey-Viking

    Joined:
    Nov 3, 2012
    Posts:
    4
    There's a very good reason - I was doing it wrong :) After a night's sleep and some more Googling, I have a working system which adds and removes the Disabled tag from Entities. For future reference, this is what I came up with.

    In my Bootstrap MonoBehaviour, I generate a random set of voxels in the unit cube:
    Code (CSharp):
    1. private void Start() {
    2.         _manager = World.Active.GetOrCreateManager<EntityManager>();
    3.  
    4.         _arch = _manager.CreateArchetype(
    5.             typeof(Position),
    6.             typeof(Scale),
    7.             typeof(Disabled),
    8.             typeof(LocalToWorld),
    9.             typeof(Voxel));
    10.  
    11.         _renderer = new MeshInstanceRenderer {
    12.             mesh = TheMesh,
    13.             material = TheMaterial,
    14.             castShadows = ShadowCastingMode.Off,
    15.             receiveShadows = false,
    16.             subMesh = 0
    17.         };
    18.  
    19.         AddVoxels(0, 0.25f);
    20.         AddVoxels(1, 0.25f);
    21.         AddVoxels(2, 0.25f);
    22.         AddVoxels(3, 0.25f);
    23.         AddVoxels(4, 0.25f);
    24.         AddVoxels(5, 0.25f);
    25.         AddVoxels(6, 0.25f);
    26.     }
    27.  
    28.     private void AddVoxels(int depth, float density) {
    29.         Depth d = new Depth {Value = depth};
    30.        
    31.         int cellCount = (int) Mathf.Pow(2.0f, depth);
    32.         float voxelSize = Size / cellCount;
    33.         int numVoxels = Mathf.CeilToInt((cellCount * cellCount * cellCount) * density);
    34.         Random r = new Random(23);
    35.         float3 scale = new float3(voxelSize);
    36.  
    37.         for (int i = 0; i < numVoxels; ++i) {
    38.             Entity entity = _manager.CreateEntity(_arch);
    39.             int3 idx = r.NextInt3(cellCount);
    40.             float3 pos = ((idx + new float3(0.5f)) * voxelSize) - (Size / 2.0f);
    41.             _manager.SetComponentData(entity, new Position {Value = pos});
    42.             _manager.SetComponentData(entity, new Scale {Value = scale});
    43.             _manager.SetComponentData(entity, new Voxel {Depth = depth, Index = idx});
    44.             _manager.AddSharedComponentData(entity, _renderer);
    45.             _manager.AddSharedComponentData(entity, d);
    46.         }
    47.     }
    Then my VoxelSystem looks like this:

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4.  
    5. // ReSharper disable once UnusedMember.Global
    6. public class VoxelSystem : JobComponentSystem {
    7.     private int _depth;
    8.     private int _prevDepth;
    9.     private ComponentGroup _voxelsDisabled;
    10.     private ComponentGroup _voxelsEnabled;
    11.  
    12. #pragma warning disable 649
    13.     [Inject] private EndFrameBarrier _endFrameBarrier;
    14. #pragma warning restore 649
    15.    
    16.     private struct EnableJob : IJob {
    17.         [ReadOnly] public EntityArray Entities;
    18.         [ReadOnly] public Disabled DC;
    19.         public EntityCommandBuffer CommandBuffer;
    20.  
    21.         public void Execute() {
    22.             for (int i = 0; i < Entities.Length; ++i) {
    23.                 CommandBuffer.AddComponent(Entities[i], DC);
    24.             }
    25.         }
    26.     }
    27.  
    28.     private struct DisableJob : IJob {
    29.         [ReadOnly] public EntityArray Entities;
    30.         public EntityCommandBuffer CommandBuffer;
    31.        
    32.         public void Execute() {
    33.             for (int i = 0; i < Entities.Length; ++i) {
    34.                 CommandBuffer.RemoveComponent<Disabled>(Entities[i]);
    35.             }
    36.         }
    37.     }
    38.  
    39.     protected override void OnCreateManager() {
    40.         _voxelsDisabled = GetComponentGroup(
    41.             typeof(Depth),
    42.             typeof(Disabled));
    43.         _voxelsEnabled = GetComponentGroup(
    44.             ComponentType.Subtractive<Disabled>(),
    45.             typeof(Depth));
    46.  
    47.         _prevDepth = 99999;
    48.         GameManager.OnDepthChanged += delegate(int depth) { _depth = depth; };
    49.     }
    50.  
    51.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    52.         if (_prevDepth == _depth) {
    53.             return inputDeps;
    54.         }
    55.        
    56.         _voxelsEnabled.SetFilter(new Depth {Value = _prevDepth});
    57.         EnableJob job1 = new EnableJob {
    58.             Entities = _voxelsEnabled.GetEntityArray(),
    59.             DC = new Disabled(),
    60.             CommandBuffer = _endFrameBarrier.CreateCommandBuffer()
    61.         };
    62.  
    63.         _voxelsDisabled.SetFilter(new Depth {Value = _depth});
    64.         DisableJob job2 = new DisableJob {
    65.             Entities = _voxelsDisabled.GetEntityArray(),
    66.             CommandBuffer = _endFrameBarrier.CreateCommandBuffer()
    67.         };
    68.  
    69.         JobHandle h1 = job1.Schedule(inputDeps);
    70.         JobHandle h2 = job2.Schedule(h1);
    71.  
    72.         _prevDepth = _depth;
    73.  
    74.         return h2;
    75.     }
    76. }
    Of course, I'm not claiming this is the *best* way of doing it, but it certainly works. The jobs could be run at the same time, for instance. Using IJobParallelFor doesn't work because I am writing to the CommandBuffer, but even with 100,000+ voxels, the tasks aren't too onerous, and barely register in the profiler.

    I'd still like to investigate toggling the MeshInstanceRenderer for each level, rather than toggling each voxel, which strikes me as a more scalable solution.
     
  4. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    64
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    Use Concurrent version for that.
    FYI Inject deprecated, get barrier from World property in OnCreateManager and use
    AddJobHandleForProducer in OnUpdate (for correct dependencies)
     
  6. Mersey-Viking

    Mersey-Viking

    Joined:
    Nov 3, 2012
    Posts:
    4
    Ah yes, thanks eizenhorn. I had read somewhere that Inject is deprecated, but many of the posts and examples out there still use it. But now you have revealed how I can get hold of a command buffer, I'll use it right away.

    Thanks Draveler, that was certainly on my list of optimisations, but couldn't get my head around it. Now I have a working codebase, I can have another look at your suggestion.