1

I have a specific way of generating meshes and structuring my scene that makes occlusion culling very straight forward and optimal. Now all I need is to know how to actually show or hide a mesh efficiently using the ECS hybrid renderer. I considered changing the layer to a hidden layer in the RenderMesh component but the RenderMesh component is an ISharedComponentData and so does not support jobification or burst. I saw the Unity BatchRendererGroup API and it looked promising with its OnPerformCulling callback but I don't know if it is possible to hook into the HybridRenderSystem's internal BatchRenderGroup. I also saw the DisableRendering IComponentData tag that I guess disables an entities rendering. However, again, this can only be done from the main thread. I can write my own solution to render meshes using Graphics.DrawMesh or something like it, but I would prefer to integrate it natively with HybridRenderer in order to also cull meshes that are not related to my procedural meshes.

Is any of this possible? What is the intended use?

3 Answers3

1

I'm not sure it's the best option but you can maybe try parallel command buffer:

var ecb = new EntityCommandBuffer( Allocator.TempJob );
var cmd = ecb.AsParallelWriter();

/* job 1 executes with burst & cmd adds/removes Disabled or DisableRendering tags */

// (main thread) job 2 executes produced commands:
Job
    .WithName("playback_commands")
    .WithCode( () =>
    {
        ecb.Playback( EntityManager );
        ecb.Dispose();
    }
    ).WithoutBurst().Run();
Andrew Łukasik
  • 1,454
  • 12
  • 19
  • I had thought of this and... it seems like the most likely option although a little counterintuitive considering Hybrid Renderer is supposed to be designed for ECS. It may not end up being that bad, if I only ever add/remove the component where necessary I doubt there will be to many to process on the main thread. It would just be nice to have native support for this kind of thing. – Christopher Silvas Aug 10 '20 at 19:39
  • Thanks btw! Just a further question; with the EntityCommandBuffer, can I check if an entity already has the DisableRendering component? I would need to know this in order to add or remove it depending on if it should be culled or not. – Christopher Silvas Aug 10 '20 at 19:42
  • Or if I add a component that is already on an entity using the EntityCommandBuffer does it just do nothing? This is a possible route but it creates the problem that I would have to evaluate every mesh on the main thread to conditionally add or remove a component. – Christopher Silvas Aug 10 '20 at 19:45
  • Parallel writer is designed for fast write queue operations - so no reading ie. no `HasComponent`. There is this `var disabledData = GetComponentDataFromEntity( isReadOnly:true );` for random read commands (`if( disabledData.HasComponent(entity) ){/*entity is disabled*/}`). Warning: random memory access slows down execution (cache miss) so you may want to design your code so that's not required somehow. – Andrew Łukasik Aug 10 '20 at 19:51
  • Oh I forgot to mention - you should **definitely** iterate by chunks here. Because **entities in chunks share component layout** you don't have to check if given entity is disabled or not (!) - you just iterate over chunks with/-out `Disabled` tag and that's it. Should play nicely with parallel ecb too. – Andrew Łukasik Aug 10 '20 at 20:22
  • Ahhh, that is a great idea! Write two jobs, one for entities with the component, the other for entities without and have them test for opposite things in order to either remove or add the Disabled component (I have never heard of the Disabled component but I assume it disables rendering and likely more). I am not familiar with iterating by chunk, what would the API calls look like for iterating on chunks with RenderBounds and LocalToWorld components? Do I implement a struct with an IJobChunk interface? Or is there an Entities.ForXXXXX implementation for this kind of thing? – Christopher Silvas Aug 10 '20 at 21:53
  • Chunks are definitely more low-level ecs, so no `Chunks.ForEach` here (so far). One fast way of iterating over them is `IJobChunk` indeed and [documentation contains probably all the instructions you need there.](https://docs.unity3d.com/Packages/com.unity.entities@0.13/manual/chunk_iteration_job.html) – Andrew Łukasik Aug 10 '20 at 22:38
  • 1
    I am back! I implemented your suggestion and was able to get pretty good results. I have at least confirmed that my occlusion culling technique is viable and efficient. I am seeing speedups of around 1.5 to 2.5x, depending on the scenario. I am however finding that the ECB is a rather large bottleneck (spikes of 10ms+ when quick turning camera) so I will look into alternate possibilities. I also ran into the problem that the "Disabled" component also disables the mesh for shadow casting... It looks like I need a way to tie my occlusion culling into each camera including lights, individually. – Christopher Silvas Aug 11 '20 at 06:14
  • That 10ms can probably be spread over 4 frames if you cleverly cap its workload somehow. It may result it objects popping in/out but thats probably still better. – Andrew Łukasik Aug 11 '20 at 06:35
  • I use this technique already with my occlusion structure generation, the job which determins visibility depends on my structure being generated. Meaning I must do all visibility checks/updates at once when my structure is finished generating across frames. I have thought about it and I think I will extend the URP and inject my own code. – Christopher Silvas Aug 11 '20 at 13:01
  • ECB is a dead end here then – Andrew Łukasik Aug 11 '20 at 16:24
0

There is another way of hiding/showing entities. But it requires you to group adjacent entities in chunks spatially (you're probably doing that already). Then you will be occluding not specific entities one by one but entire chunks of them (sectors of space). It's possible thanks to fabulous powers of:

chunk component data

var queryEnabled = EntityManager.CreateEntityQuery(
        ComponentType.ReadOnly<RenderMesh>()
    ,   ComponentType.Exclude<Disabled>()
);

queryEnabled.SetSharedComponentFilter( new SharedSector {
        Value = new int3{ x=4 , y=1 , z=7 }
} );

EntityManager.AddChunkComponentData( queryEnabled , default(Disabled) );
// EntityManager.RemoveChunkComponentData<Disabled>( queryDisabled );

public struct SharedSector : ISharedComponentData
{
    public int3 Value;
}
Andrew Łukasik
  • 1,454
  • 12
  • 19
0

The answer is that you can't and you shouldn't! Unity Hybrid rendering gets its speed by laying out data in sequence. If there was a boolean for each mesh that allowed you to show or hide the mesh Unity would still have to evaluate it which I guess is not in the their design philosophy. This whole design philosophy in general did not work out for me as I found out.

My world is made up of chunks of procedurally generated terrain meshes (think minecraft but better ;)) The problem with this is that each chunk has its own RenderMesh with a unique mesh... meaning that each chunk gets its own... chunk... in memory xD. Which, as appropriate as that sounds, is extremely inefficient. I decided to abandon Hybrid ECS all together and use good old game objects. With this change alone I saw a performance boost of 4x (going from 200 to 800fps). I just used the MeshRenderer.enabled property in order to efficiently enable and disable rendering. To jobify this I simply stored an array of the mesh bounds and a boolean for if it is visibile or not. This array I could then evaluate in a job and spit back out an index list of all the chunks that needed their visibility changed. This leaves only setting a few boolean values for the main thread which is not very expensive at all... It is not the ECS friendly solution I was looking for but from the looks of it, ECS was not exactly my friend here. Having unique meshes for each section of my world was clearly not the intended use case of Hybrid ECS.