Search Unity

Changing materials for MeshInstanceRenderer in a system

Discussion in 'Graphics for ECS' started by madks13, Aug 6, 2018.

  1. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    Hello,

    i have blocks (quads) with a position and an index indicating the type of texture i want on it.

    I tried to simply change it's color first to a simple color in order to have visual confirmation the code worked, but it doesn't. I found, on this forum, a few posts explaining how to change colors, but all the code i saw was instancing new MeshInstanceRenderer structs, which i found weird. Is there a better way to change the color/material of an existing entity?

    Here is the code i have so far :

    Code (CSharp):
    1.  
    2.     public struct BlockData : IComponentData
    3.     {
    4.         public int Index;
    5.     }
    Code (CSharp):
    1.  
    2.     public class BlockSystem : ComponentSystem
    3.     {
    4.         struct Data
    5.         {
    6.             public readonly int Length;
    7.             public ComponentDataArray<Position> Position;
    8.             public ComponentDataArray<BlockData> Block;
    9.             public ComponentArray<MeshInstanceRendererComponent> Mesh;
    10.         }
    11.  
    12.         [Inject] Data m_Data;
    13.  
    14.         protected override void OnUpdate()
    15.         {
    16.             if (m_Data.Length == 0)
    17.             {
    18.                 return;
    19.             }
    20.  
    21.             for (int i = 0; i < m_Data.Length; i++)
    22.             {
    23.                 var position = m_Data.Position[i];
    24.                 var data = m_Data.Block[i];
    25.                 var renderer = m_Data.Mesh[i];
    26.  
    27.                 renderer.Value.material.color = Color.blue;
    28.  
    29.             }
    30.         }
    31.     }
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You don't want to use the <[Shared]ComponentData>Component. That just holds the original [Shared]ComponentData and is not updated (except in inspector for debugging), you want to manipulate the actual [Shared]ComponentData.

    Code (CSharp):
    1. public SharedComponentDataArray<MeshInstanceRenderer>Mesh;
    That said, in this case I'm not sure why it wouldn't work anyway as Material is a reference.
     
    Last edited: Aug 6, 2018
    madks13 likes this.
  3. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    Thanks for that advice @tertle . I tried it but now i'm getting an error :

    Code (CSharp):
    1. ArgumentException: BlockSystem:m_Data SharedComponentDataArray<> must always be injected as [ReadOnly]
    I tried adding the [ReadOnly(true)] attribute, with readonly modifier and both at same time. Is there some other way? Also, i left it inside the Data struct, should i move it outside?
     
    Last edited: Aug 6, 2018
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You're using wrong attribute, the ReadOnly you want has an empty constructor

    The ReadOnly you want is within the Unity.Collections namespace
    I suspect you're using the ReadOnly attribute from System.ComponentModel which your IDE often suggests
     
    madks13 likes this.
  5. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    @tertle worked like a charm. Thanks for the help.
     
  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    You want to use EntityManager.GetSharedComponentData to get the shared component data struct, replace the material to point to a different one and then call EntityManager.SetSharedComponentData.
     
  7. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    @Joachim_Ante I need an entity for using GetSharedComponentData and SetSharedComponentData. But i only got Position and BlockData. How would i get the related entity? Also, is it absolutely necessary in order to change the texture of the block?
     
  8. dstilwagen

    dstilwagen

    Joined:
    Mar 14, 2018
    Posts:
    36
    You can use the EntityArray type inside your Data struct and access it just like your other data.
     
  9. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    @dstilwagen Thanks for the tip.

    I got one last question concerning this matter :

    No matter how i try, i can't find a solution to my multiple texture problem other than having one MeshInstanceRenderer per texture. This is because each block will have to show a part of the texture depending on it's position, the size of the texture and the type of block (one hi-res texture per type). I looked at Texture array, but this would only force me to have one MeshInstanceRenderer per block.

    So i wanted to ask, is there an efficient way of creating one MeshInstanceRenderer per texture (contained in a list) other than instantiating them one by one while copying the shared properties (material, shadowcasting...)?
     
  10. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    @dstilwagen I tried using the EntityArray but i'm getting access denied error message :

    The error
    Code (CSharp):
    1. InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it
    And here is the code
    Code (CSharp):
    1.  
    2.     public class BlockSystem : ComponentSystem
    3.     {
    4.         struct Data
    5.         {
    6.             public readonly int Length;
    7.             public ComponentDataArray<BlockData> Block;
    8.             public EntityArray Entities;
    9.         }
    10.  
    11.         [Inject] Data m_Data;
    12.  
    13.         protected override void OnUpdate()
    14.         {
    15.             if (m_Data.Length == 0)
    16.             {
    17.                 return;
    18.             }
    19.  
    20.             for (int i = 0; i < m_Data.Length; i++)
    21.             {
    22.                 var data = m_Data.Block[i];
    23.  
    24.                 //Block display needs updating
    25.                 if (data.CodeChanged != 0)
    26.                 {
    27.                     var textureData = ResourceManager.Textures.First(t => t.Code == data.Code);
    28.                  
    29.                     var entity = m_Data.Entities[i];
    30.  
    31.                     EntityManager.SetSharedComponentData(entity, textureData.MIR);
    32.  
    33.                     data.CodeChanged = 0;
    34.                 }
    35.             }
    36.         }
    37.     }
    I'm trying to set the MeshInstanceRenderer for the entity based on the property Code, which indicates the type of block.

    Edit : i found this post https://forum.unity.com/threads/inv...ocated-it-is-not-allowed-to-access-it.524779/

    It says to use PostUpdateCommands. I changed the line to

    Code (CSharp):
    1.                     this.PostUpdateCommands.SetSharedComponent(entity, textureData.MIR);
    2.  
    and now there are no errors, however, the display is not changing. All the blocks have the same default texture. I checked and the textures i'm trying to assign are different. I also changed the reference material, but the blocks weren't reacting to those changes meaning they should have at least one copy of the reference material.
     
    Last edited: Aug 8, 2018
  11. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    If you haven't resolved yet ,check if you can replace Instance renderer, using SetSharedComponent, withouth issue. Works for me.
    For example
    Code (CSharp):
    1.  commandsBuffer.SetSharedComponent <MeshInstanceRenderer> ( entity,  renderer ) ;
     
  12. madks13

    madks13

    Joined:
    May 8, 2016
    Posts:
    173
    Oh sorry, i thought i said in here that it was solved. Maybe i talked about in in another post and mixed the two. In any case, the problem i was having is due to trying to mix OOP with ECS on a subconscious level. Once i understood that, i got what was wrong in my way of doing.

    For anyone else having the same problem, my current solution :
    - I'm instancing entities in one place, a system that knows there are entities that should visually represent the blocks, and makes them with MeshInstanceRenderer (i'm calling it MIR from now on, too long of a name) added from the start
    - In another system, it catches all entities having a specific ComponentData, updates the MIR, and removes said component
    - This uses the SetSharedComponentData method.
    - Each block has each own copy of MIR, copied from a structure containing the texture information along with pre configured MIR. That way i only have to find the texture index, and give the contained MIR to the method when i call it.
     
    Last edited: Oct 24, 2018
  13. purpl3grape

    purpl3grape

    Joined:
    Oct 18, 2015
    Posts:
    8
    Hi I've been wondering how to update the MeshInstanceRenderer material for desired entities, and read some feedback and comments here. Following Joachim's advice, regarding 1) GetSharedComponentData, 2) Pointing the material to the desired one, 3) SetSharedComponentData.

    I see the correct results in the editor, however there are a couple errors flagged in console:
    - "The component has not been added to the entity."
    - "A Native Collection has not been disposed, resulting in a memory leak"

    Here's a video recording of the editor in play mode:


    This is the method that causes the issue:
    Code (CSharp):
    1. private void AnimateEntities_Run(EntityManager entityManager)
    2.     {
    3.         NativeArray<Entity> entities = entityManager.GetAllEntities(Allocator.Temp);
    4.  
    5.         if (entities.Length <= 0)
    6.         {
    7.             Debug.Log("No Entities to animate");
    8.             entities.Dispose();
    9.             return;
    10.         }
    11.         for(int i = 0; i < entities.Length; i++)
    12.         {
    13. MeshInstanceRenderer newrend = entityManager.GetSharedComponentData<MeshInstanceRenderer>(entities[i]);
    14. newmesh.material = Material2;
    15. entityManager.SetSharedComponentData(entities[i], newrend);
    16.         }
    17.         entities.Dispose();
    18.     }
    * This is a pure ECS approach, and I'm just handling the Animation Updates (Baked Animations into material) within the 'bootstrap' spawner script I have via KeyPresses.

    I'd really appreciate your feedback on this or if you need more information please let me know. What am I doing wrong?

    EDIT --> Oops, I found the bug, I had another Existing Hybrid entity serving as the main camera currently which was causing the issue due to it not having a MeshInstanceRenderer Component.

    So just threw in a check at the start of the for loop:

    Code (CSharp):
    1. for(int i = 0; i < entities.Length; i++)
    2.         {
    3.             if (!entityManager.HasComponent<MeshInstanceRenderer>(entities[i]))
    4.             {
    5.                 continue;
    6.             }
    7. MeshInstanceRenderer newrend = entityManager.GetSharedComponentData<MeshInstanceRenderer>(entities[i]);
    8. newmesh.material = Material2;
    9. entityManager.SetSharedComponentData(entities[i], newrend);
    10.         }
    This way, is a bit expensive in terms of GC allocated, I noticed from the profiler however. Is there an approach that does not cause the GC allocations?
     
    Last edited: Oct 17, 2018
  14. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Why you just not filter entities or get specific archetype/chunq query? In this case you not need this checks.