Search Unity

How to manipulate transform matrix required for MeshInstaceRenderer in pure ECS?

Discussion in 'Graphics for ECS' started by Mr-Mechanical, Nov 5, 2018.

  1. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    What's the best way to manipulate transforms with a pure ECS system? I've been enjoying the remarkable performance benefits of this incredible ECS system. Trying to look around for resources pertaining to manipulating transforms.

    Thanks!

    EDIT: I didn't realize I should just maneuver around using transform by MeshInstanceRenderer/TransformMatrix if I want to use pure ECS. This makes a lot more sense. No need for GameObjects except for bootstrap script.
     
    Last edited: Nov 6, 2018
  2. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Last edited: Nov 6, 2018
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Ahhhh, the article is quite outdated. Right now the scheduling's batch count cannot be specified anymore.

    The other inject way is that you just add `public TransformAccessArray taa;` in your struct and it is immediately there on each OnUpdate. It only works if your entity contains ComponentData<Transform> (In other words, being a hybrid entity)

    Inject is being deprecated so an equivalent would be using `componentGroup.GetTransformAccessArray()` Ensure that the group has component data UnityEngine.Transform. You can look at Unity's CopyTransformFromGameObjectSystem.cs StashTransforms job.

    Creating TAA manually has potential to combine transforms from different sources and arrange them in your convention. I have a job where it can move both root transform and child transform of an object together, but GameObjectEntity can only capture root Transform component so I manually assemble the other transforms.

    And by definition TAA is not pure. The code are all in hybrid packages because it needs to access mono Transform component.
     
    Mr-Mechanical likes this.
  4. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Thanks for the help. Everything makes a lot more sense since I've now just learned of MeshInstanceRenderer/TransformMatrix. I'm trying to stay pure and I've realized this is what I'm looking for.
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    You don't need use Transform in pure ECS. You must use pure separated Position\Rotation\Scale IComponentDatas.
     
    Mr-Mechanical likes this.
  6. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Thanks for the help @eizenhorn. I now realize I'm interested in the transform matrix required for rendering created from the components you mentioned.


    Here are some questions that I am doing my best to look into:
    How should I update the matrix used by MeshInstanceRenderer? I've been looking at TransformSystem:
    https://github.com/Unity-Technologi...a/Documentation/reference/transform_system.md

    If I understand correctly, any entity with a position, rotation, scale will automatically receive an automatically computed matrix component. What about entities that are not used with rendering systems and have a position component but don't need a matrix for rendering? Are there other required components I have missed? Are TransformMatrix and Transform System independent features?
     
    Last edited: Nov 8, 2018
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    If you look to source code you'll see, TransformSystem creates LocalToWorld matrix, it's required for correct calculation position and\or rotation and\or scale for objects in world and with\wihtout hierarchy, but for rendering uses other matrix - VisibleLocalToWorld (but she calculates based on LocalToWorld)
    upload_2018-11-8_10-15-23.png
    upload_2018-11-8_10-24-53.png
    and calcualted by MeshInstanceRendererSystem.
    upload_2018-11-8_10-16-1.png
    Thus, if object not use MeshInstanceRenderer than not use VisibleLocalToWorld, but it still use own LocalToWorld matrix provided by TransformSystem
    (For root local for example)
    Code (CSharp):
    1. [BurstCompile]
    2.     private struct RootLocalToWorld : IJobParallelFor
    3.     {
    4.       [DeallocateOnJobCompletion]
    5.       [ReadOnly]
    6.       public NativeArray<ArchetypeChunk> chunks;
    7.       [ReadOnly]
    8.       public ArchetypeChunkComponentType<Rotation> rotationType;
    9.       [ReadOnly]
    10.       public ArchetypeChunkComponentType<Position> positionType;
    11.       [ReadOnly]
    12.       public ArchetypeChunkComponentType<Scale> scaleType;
    13.       public ArchetypeChunkComponentType<LocalToWorld> localToWorldType;
    14.  
    15.       public void Execute(int chunkIndex)
    16.       {
    17.         ArchetypeChunk chunk = this.chunks[chunkIndex];
    18.         int count = chunk.Count;
    19.         NativeArray<Rotation> nativeArray1 = chunk.GetNativeArray<Rotation>(this.rotationType);
    20.         NativeArray<Position> nativeArray2 = chunk.GetNativeArray<Position>(this.positionType);
    21.         NativeArray<Scale> nativeArray3 = chunk.GetNativeArray<Scale>(this.scaleType);
    22.         NativeArray<LocalToWorld> nativeArray4 = chunk.GetNativeArray<LocalToWorld>(this.localToWorldType);
    23.         if (!(chunk.DidAddOrChange<Rotation>(this.rotationType) | chunk.DidAddOrChange<Position>(this.positionType) | chunk.DidAddOrChange<Scale>(this.scaleType)))
    24.           return;
    25.         bool flag1 = nativeArray1.Length > 0;
    26.         bool flag2 = nativeArray2.Length > 0;
    27.         bool flag3 = nativeArray3.Length > 0;
    28.         if (((flag2 ? 0 : (!flag1 ? 1 : 0)) & (flag3 ? 1 : 0)) != 0)
    29.         {
    30.           for (int index = 0; index < count; ++index)
    31.             nativeArray4[index] = new LocalToWorld()
    32.             {
    33.               Value = float4x4.Scale(nativeArray3[index].Value)
    34.             };
    35.         }
    36.         else if (!flag2 & flag1 && !flag3)
    37.         {
    38.           for (int index = 0; index < count; ++index)
    39.             nativeArray4[index] = new LocalToWorld()
    40.             {
    41.               Value = new float4x4(nativeArray1[index].Value, new float3())
    42.             };
    43.         }
    44.         else if (!flag2 & flag1 & flag3)
    45.         {
    46.           for (int index = 0; index < count; ++index)
    47.             nativeArray4[index] = new LocalToWorld()
    48.             {
    49.               Value = math.mul(new float4x4(nativeArray1[index].Value, new float3()), float4x4.Scale(nativeArray3[index].Value))
    50.             };
    51.         }
    52.         else if (flag2 && !flag1 && !flag3)
    53.         {
    54.           for (int index = 0; index < count; ++index)
    55.             nativeArray4[index] = new LocalToWorld()
    56.             {
    57.               Value = float4x4.Translate(nativeArray2[index].Value)
    58.             };
    59.         }
    60.         else if (((!flag2 ? 0 : (!flag1 ? 1 : 0)) & (flag3 ? 1 : 0)) != 0)
    61.         {
    62.           for (int index = 0; index < count; ++index)
    63.             nativeArray4[index] = new LocalToWorld()
    64.             {
    65.               Value = math.mul(float4x4.Translate(nativeArray2[index].Value), float4x4.Scale(nativeArray3[index].Value))
    66.             };
    67.         }
    68.         else if (flag2 & flag1 && !flag3)
    69.         {
    70.           for (int index = 0; index < count; ++index)
    71.             nativeArray4[index] = new LocalToWorld()
    72.             {
    73.               Value = new float4x4(nativeArray1[index].Value, nativeArray2[index].Value)
    74.             };
    75.         }
    76.         else
    77.         {
    78.           if (!(flag2 & flag1 & flag3))
    79.             return;
    80.           for (int index = 0; index < count; ++index)
    81.             nativeArray4[index] = new LocalToWorld()
    82.             {
    83.               Value = math.mul(new float4x4(nativeArray1[index].Value, nativeArray2[index].Value), float4x4.Scale(nativeArray3[index].Value))
    84.             };
    85.         }
    86.       }
    87.     }
     
    Last edited: Nov 8, 2018
    Mr-Mechanical likes this.
  8. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    If you have any of Position, Rotation, Scale : LocalToWorld matrix is automatically added.

    If you have any of Position, Rotation, Scale : LocalToWorld matrix is automatically updated, you don't have to update it. If you update it, the value would be soon overwritten by what's calculatable from Position, Rotation, Scale.

    If you want to update the matrix manually, ensure you have no Position, Rotation, and Scale.

    You can try adding the LocalToWorld in edit mode while having either Position, Rotation, or Scale. Then change Position, Rotation, or Scale's value. You will see the matrix change accordingly.
     
    Mr-Mechanical likes this.
  9. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Thank you guys for the valuable insight.

    This makes me curious...
    Does that mean TransformMatrix component is not necessary?
    https://docs.unity3d.com/Packages/c...sforms.TransformMatrix.html?q=TransformMatrix
    https://docs.unity3d.com/Packages/c...api/Unity.Rendering.MeshInstanceRenderer.html

    Also, if an Entity stores a position but doesn't need to have a hierarchy shall I avoid the name "Position" for components to ensure no unnecessary calculation by TransformSystem for matrices?

    (Edited for clarification)
     
    Last edited: Nov 9, 2018
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    TransformMatrix deprecated and not exists in current versions, it's evolve to LocalToWorld matrix and it's applied automatically by TransformSystem for entities with position and\or rotation and\or scale
    If it not have MeshInstanceRenderer it's not used in MeshInstanceRenderSystem and not calculate visibility matrices
     
    Mr-Mechanical likes this.
  11. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Cool! Your post clears up a lot. Now I don't have to worry about TransformMatrix.

    So I don't have to be concerned about unnecessary matrix computation since an entity won't be considered by TransformSystem without a shared MeshInstanceRenderer, right?

    Thanks a lot this is very helpful.
     
  12. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    No. You change your question :)
    Again.
    If entity HAS Position and\or Rotation and\or Scale, then entity USES in TransformSystem.
    If entity HAS MeshInstanceRenderer, then entity USES in MeshInstanceRenderSystem.
    (also exists Disable, Frozen, Static components for enable\disable entities for using in systems, but this other story)
     
    lclemens and Mr-Mechanical like this.
  13. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    I see. The TransformSystem always queries for those specific components. So if I'm simply storing only a position and I do not need a matrix for hierarchy transformations, I should avoid naming my component "Position" and the Entity with that component won't receive an automatically updated LocalToWorld.

    That should be it, I think I understand finally. Again, I really appreciate the help, this was of great use to me.
     
    Last edited: Nov 9, 2018
  14. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Just to add more info, from TransformSystem.cs things work by chunk iteration. Chunks to work with are made from ComponentGroup. You can see all the groups in `void GatherQueries()` The group is created by ArchetypeChunkQuery.

    The "automatically add LocalToWorld" feature works on a group called `NewRootGroup`

    Code (CSharp):
    1.             NewRootGroup = GetComponentGroup(new EntityArchetypeQuery
    2.             {
    3.                 Any = new ComponentType[] {typeof(Rotation), typeof(Position), typeof(Scale)},
    4.                 None = new ComponentType[] {typeof(Frozen), typeof(Parent), typeof(LocalToWorld), typeof(Depth)},
    5.                 All = Array.Empty<ComponentType>(),
    6.             });
    The interpretation is, you could prevent an addition of matrix by already having Frozen/Parent/Depth on your object. Frozen is attachable at edit mode too. Not sure about the other two.

    The "automatically calculate the added LocalToWorld" feature works on a group called `RootLocalToWorldGroup`

    Code (CSharp):
    1.             RootLocalToWorldGroup = GetComponentGroup(new EntityArchetypeQuery
    2.             {
    3.                 Any = new ComponentType[] {typeof(Rotation), typeof(Position), typeof(Scale)},
    4.                 None = new ComponentType[] {typeof(Frozen), typeof(Parent)},
    5.                 All = new ComponentType[] {typeof(LocalToWorld)},
    6.             });
    That is, even if you have the matrix already auto generated + any of P R S, if you have Frozen or Parent on an object no calculation will be made to the matrix.

    So your solution might be using Frozen, if you wish to reuse Unity's Position.
     
    Gildar76 and Mr-Mechanical like this.
  15. Mr-Mechanical

    Mr-Mechanical

    Joined:
    May 31, 2015
    Posts:
    507
    Cool beans. I like the idea of not having to update the matrix by Frozen or Parent. But, I am thinking about the potential memory cost of an additional 4x4 float matrix per position. Looking at docs I am a little scared about freezing still adding a matrix to entities with "Position". Though it's an interesting idea worth looking into.

    https://github.com/Unity-Technologi...a/Documentation/reference/transform_system.md
    "The process of freezing is:
    1. TransformSystem update queries for Static components.
    2. TransformSystem adds FrozenPending component.
    3. TransformSystem updates the LocalToWorld component normally.
    4. On the next TransformSystem update, queries for FrozenPending components and adds Frozen component.
    5. TransformSystem ignores Position, Rotation, or Scale components if there is an associated Frozen component."
     
  16. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    That list of process is describing exactly why having Frozen will not add a matrix. The list is named "process of freezing", meaning that it have to go from not-frozen-yet state to frozen. It is by adding Static component and wait for a frame for the matrix to appear, then at that moment attach Frozen to stop the update. This "process of freezing" ensure we have a matrix with calculated values then not update it anymore.

    If you attach Frozen manually it is like you skip to step 4., you are already frozen without the matrix. You will not have a matrix (5. says) and so not incuring 4x4 float memory cost. I don't know if this is hacky or not, since only from the docs, an existence of Frozen seems to be intended solely for the after-effect of Static. But in hybrid Unity is giving us a wrapper for Frozen (FrozenComponent : ComponentDataWrapper<Frozen>) to attach to things, so I think this "Frozen without help from Static" is a valid tactic and will stay. Might be that it is just not mentioned in the doc yet.
     
    Gildar76 and Mr-Mechanical like this.