Search Unity

Question RenderMeshUtility will not accept EntityCommandBuffer?

Discussion in 'Graphics for ECS' started by heu3becteh, Dec 7, 2022.

  1. heu3becteh

    heu3becteh

    Joined:
    Aug 6, 2020
    Posts:
    25
    While Hybrid Renderer did accept EntityCommandBuffer (https://docs.unity3d.com/Packages/c...51/api/Unity.Rendering.RenderMeshUtility.html) it seems that Entities Graphics does not (https://docs.unity3d.com/Packages/c....0/api/Unity.Rendering.RenderMeshUtility.html), only EntityManager?

    Isn't it more convenient to put all the commands to EntityCommandBuffer instead of waiting a frame to work with freshly created Entity?
    Well... It is not like this structural change is going to happen often, could just go with EntityManager, but it is less convenient in some cases. Is there a reason for that change?
     
  2. pbhogan

    pbhogan

    Joined:
    Aug 17, 2012
    Posts:
    384
    I was about to ask the same question.

    I'm trying to upgrade some code to entities 1.0. It spawns procedural terrain chunks at runtime and was using an ECB to combine all the structural changes.

    What's the correct approach now?

    I'm thinking maybe instantiate the chunks using a prefab with an (empty) mesh and material and then just update the
    MaterialMeshInfo with the new mesh and material? Seems like that could be done in an ECB.

    Eventually, I plan to switch to using the BatchRendererGroup directly to render the terrain, but for now just trying to get things working in 1.0
     
  3. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    The recommended approach is indeed instantiating copies from a base entity (which can be a prefab entity or a runtime created entity, or just any entity), and then modifying the component values of individual entities.

    Also, it's worth noting that MaterialMeshInfo is a regular Burst-compatible IComponentData, so you don't even need an ECB to modify it as long as the Entity has the component to begin with. However, if you want to also do the Instantiate from a job, you still need an ECB for that.

    The sample code at https://github.com/Unity-Technologi...es/EntityCreationAPI/TestEntityCreationAPI.cs contains an example of the instantiate-and-modify approach. The sample code uses the approach where the Instantiate is also done within the job, so it uses an ECB inside the job.
     
  4. StefanWo

    StefanWo

    Joined:
    May 24, 2015
    Posts:
    124
    True, but it would be nice, that it can be done in a baker too :)
    Have the same problem here and needed to use a separate BakingSystem afterwards to have access to an EntityManger to add it. So at least a Baker would be ideal.

    Edit: Never mind, found that there is another MeshRendererBakingUtility that is able to do that.
     
    Last edited: Dec 12, 2022
    lclemens likes this.
  5. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Where is it and how did you use it? When I attempt to use MeshRendererBakingUtility the compiler complains that "'MeshRendererBakingUtility' is inaccessible due to its protection level".

    Googling MeshRendererBakingUtility comes up with only one result - this thread.
     
  6. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    That URL is 404. The correct URL is now https://github.com/Unity-Technologi...cenes/EntityCreation/TestEntityCreationAPI.cs

    In that example, RenderMeshUtility is used, which can't be used in a Baker because I'm not aware of any way to fetch an entity manager inside a baker, which is needed for RenderMeshUtility.AddComponents(). Because I'm creating a mesh from terrain, I can't use a prefab, so right now the only two choices I see are:
    1. Find a way to setup an entity for rendering inside a baker
    2. Make a special initializer system that runs once and converts the terrain to mesh at runtime via RenderMeshUtility (which will increase load times and be less efficient:()
    Any tips on how to do the first option? I tried MeshRendererBakingUtility but it's a private function that I can't use. I am trying to locate its source code but not having much luck.
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
  8. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    So by using an assembly reference I can avoid these errors?:

    OverrideMeshRendererBaker.cs(16,13): error CS0122: 'MeshRendererBakingUtility' is inaccessible due to its protection level
    OverrideMeshRendererBaker.cs(19,34): error CS0122: 'MeshRendererBakingData' is inaccessible due to its protection level
    OverrideMeshRendererBaker.cs(23,42): error CS0122: 'MeshRendererBakingData' is inaccessible due to its protection level​
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Indeed
     
  10. StefanWo

    StefanWo

    Joined:
    May 24, 2015
    Posts:
    124
    True, it's internal sry. I didn't use it, i just saw, that it exists, but for my usecase it anyway wasn't needed, so i did not try it.
     
  11. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    OK So I found a hacky solution, but it's not very generic and probably won't work for everyone. It works for me because I am generating a mesh in the baker from a game-object (Unity Terrain --> mesh and material).

    I read some tutorials on how to do assembly references, but I wasn't able to come up with a scheme for using it to access an internal function. Maybe I might be able to figure it out if I spent a long time researching it, but at this point in time, the topic is just too advanced for my Unity skillset and I don't have the time or patience it would take to deep dive into assemblies. Also I'm not sure if I would want my assemblies all rearranged anyway just to access an internal function and class.

    I looked into other ways to access an internal function, and I found nothing. The top google result is a stackoverflow question where pretty much everyone there unanimously agrees that it can't be done - so kudos to DreamingImLatios for accomplishing the impossible :)

    I'm am certain that JussiKnuuttila's idea of using RenderMeshUtility in a system at runtime would work, however, I don't want to generate the terrain mesh every time terrain loads at runtime. That is not performance-by-default.

    So it seemed that there is just no simple way to build a mesh programmatically in a Baker, and I went back to the drawing board. Of course it's a piece of cake with RenderMeshUtility and IConvertGameObjectToEntity in 0.51, but with 1.0 it's a whole different game.

    Someone in the Turbo Makes Games Discord suggested using a Baking system, which has access to EntityManager. That seemed like a great idea, but it turned out to be a dead end because Baking systems cannot access any managed code or game-objects, and since I want the baker to create a mesh from the terrain game-object, a Baking system is useless.

    Another person suggested using GameObjectConversionSystem, however, that was also not possible because it was removed in 1.0 pre.

    So then I had the idea... what if I put a dummy MeshFilter and a dummy MeshRenderer on the terrain game-object and then set the sharedMesh and sharedMaterial after creating the mesh and material in the baker. And lo and behold, it worked! Unfortunately, the editor draws both the baked mesh and the terrain game object at the same time, which is both inefficient in the editor and makes modifying the terrain heights very frustrating because the two are unsynchronized. I attempted to disable the Mesh Renderer in the editor, but if that is done, the system that is supposed to create the RenderMeshArray and other stuff like MaterialMeshInfo won't run -- it runs before the Baker, so enabling the MeshRenderer in the Baker doesn't help. I also tried DependsOn(authoring.GetComponent<MeshFilter>().sharedMesh); , but that doesn't work either because it doesn't always run the Baker after each modification (which would be horribly inefficient anyway). However, my current solution works okay IF I remember to disable the mesh renderer before I modify the terrain and then re-enable it when I'm done.

    Hopefully someday there will be a better way of creating a renderable mesh entity at bake-time. For now, I have a sub-optimal solution that is less user-friendly and less efficient than the old IConvertGameObjectToEntity method.

    You can see my code in this post https://forum.unity.com/threads/using-unity-terrain-with-dots-workflow.755105/page-2#post-8708523 .

    upload_2023-1-5_14-24-36.png
     
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I use assembly references. They are extremely easy to use in Unity.

    I'm going to propose a watered-down version of my solution step-by-step, because your frustration is painful to read. This watered-down version has one caveat, and that is that it will only work if the GameObject does NOT have a Mesh Renderer of Skinned Mesh Renderer. Other renderer types (like Terrain Renderer) should be fine. The full framework version gets around this limitation using its baking bootstrap mechanism.

    1) Somewhere, anywhere in your project, create a folder.
    2) Inside that folder, create an assembly definition reference. It is in the Assets->Create menu.
    3) The asmref will have a single inspector property requesting an asmdef file. You want the one that is named "Unity.Entities.Graphics". The little circle on the right gives you a context popup and you can type that string in to quickly find it.
    4) Now, any scripts in this folder are part of the Unity.Entities.Graphics package, so you can access internal APIs and add your own public APIs.
    5) Add the following script into the folder:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Entities;
    3. using UnityEngine;
    4.  
    5. namespace Unity.Rendering
    6. {
    7.     public abstract class OverrideMeshRendererBakerBase<T> : Baker<T> where T : Component
    8.     {
    9.         protected void BakeMeshAndMaterial(Renderer renderer, Mesh mesh, List<Material> materials)
    10.         {
    11.             MeshRendererBakingUtility.Convert(this, renderer, mesh, materials, true, out var additionalEntities);
    12.  
    13.             if (additionalEntities.Count == 0)
    14.                 AddComponent(new MeshRendererBakingData { MeshRenderer = renderer });
    15.  
    16.             foreach (var entity in additionalEntities)
    17.             {
    18.                 AddComponent(entity, new MeshRendererBakingData { MeshRenderer = renderer });
    19.             }
    20.         }
    21.     }
    22. }
    6) Now simply subclass OverrideMeshRendererBakerBase<T> instead of Baker<T> in your custom terrain baker. You don't have to change the locations of any of your code or deal with assemblies.
    7) The first argument requires a Renderer, which is used for fetching lightmap properties, rendering layers, and all of those sorts of random things. Unfortunately, Terrain isn't a Renderer, so you will need to pass in some other instance from a child or something.

    Anyways, you can take it a step further and make a clone of MeshRendererBakingUtility that works with a Terrain type instead, and also put that in your special folder. Then you'll be able to get more correct lightmapping and such.
     
    lclemens likes this.
  13. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Wow, thanks so much for this explanation! The 4th step was the main concept that I didn't understand, and it makes a lot more sense now. It's good to know that I can now use assembly references to access internal members in unity packages if needed.

    Last night I was in the TMG discord and DrBoum figured out that EntityManager can be used in a Baker via the _State variable. So I used:

    Code (CSharp):
    1. RenderMeshUtility.AddComponents(GetEntity(), _State.World.EntityManager, renderMeshDesc, renderMeshArray, matMeshInfo);
    And it works! It's much better than the "dummy" mesh renderer and mesh filter method I had before. So at this point, I have found 3 options for creating a renderable mesh and material entity in a Baker:
    1. Use a dummy mesh filter and mesh renderer (not the best options, but it works)
    2. Use an assembly reference to access the internal class MeshRendererBakingUtility
    3. Use _State.World.EntityManager .
    I think the 3rd option (_State) is the easiest of the 3, so I will use that unless you can think of a reason why it might not work as well as the 2nd option (assembly reference).

    Anyway, I can't thank you enough for your help.
     
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    That method I believe completely bypasses all of the state tracking which is used for internal baking (and is used to revert changes for incremental rebakes), so you may get incorrect results while making edits in the inspector for the component the baker runs for.
     
    lclemens likes this.
  15. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Ugh, you're right. I will rework my code to use the assembly method and update my code in my post in the other thread. Thanks again man... you rock!!
     
  16. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    There's something I've been wondering about.... In my code (using EntityManager in Baker), I used:
    Code (CSharp):
    1. Terrain terrain = authoring.GetComponent<Terrain>();
    2. DependsOn(terrain);
    Isn't that enough to keep track of incremental changes and internal baking?
     
  17. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    First off, you can just use this.GetComponent<Terrain>() and then you don't need the DependsOn(terrain).

    Second, what that does is it says "whenever Terrain component is modified in the inspector, undo whatever this baker did with this authoring component the last time it ran and then rerun the baker". The problem with using EntityManager in a Baker is that your manipulations aren't recorded for the "undo" part.
     
    lclemens likes this.
  18. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    That makes sense, thanks.

    What would happen if I were to use a managed component containing the terrain collider with a
    BakingSystem (which has access to EntityManager but supposedly isn't supposed to access managed code)? Hahaha - I know there already is a good solution with assembly references, but I'm mainly just curious to know the limits of baking to better understand them.
     
  19. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Baking systems are really difficult to get right. Not even Unity's packages get it right consistently. But long story short, baking systems are responsible for recognizing that some change in a baker was undone just from looking at the world and components on entities, and then undo whatever extra effects the baking systems had. Besides that, if a subscene can serialize it, you can do it. Just be careful, because baking systems run very frequently in the editor and can quickly make all your interactions with the inspector sluggish.
     
    lclemens likes this.