Unity Entity Component System --- TransformSystem

Section 1: Non-hierarchical Transforms(Basic)

LocalToWorld(float4X4)表示从本地到世界空间的变换。这是一种经典的表示方式,也是系统中唯一用来访问本地空间的组件。

  • 一些DOTS特性,只有存在LocalToWorld时才能执行。
  • 例如,RenderMesh组件依赖LocalToWorld来渲染实例。
  • 如果只有一个LocalToWorld组件,那么没有transform system会写或影响LocalToWorld数据(Translation,Rotation,Scale组件配合)。
  • 如果没有其它的变换组件添加到该实体,则用户可以直接修改LocalToWorld的数据来为实例定义方位变换。

所有的transform systems和其它的transform components都是用来提供接口来更新LocalToWorld的数据。

LocalToWorld = Translation * Rotation * Scale。

如果有Translation(float3),Rotation(quaternion),Scale(float)和LocalToWorld同时存在,那么transform system会把这些组件组合并写到LocalToWorld中。

下面列出具体的组合:

  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * Rotaion
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * Rotation * Scale
  • [TRSToLocalToWorldSystem] LocalToWorld <= Rotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Rotation * Scale
  • [TRSToLocalToWorldSystem] LocalToWorld <= Scale

Section 2: Hierarchical Transforms (Basic)

transform system 利用 LocalToParent 和 Parent 组件来为 LocalToWorld 计算基于层级的 transform。

  • LocalToParent(float4X4)表示本地空间到父节点本地空间的变换。
  • Parent(实体) 引用了父节点的 LocalToWorld。
  • 如果实体没有其它的transform system定义对LocalToParent的更新,那么可以直接用代码更新 LocalToParent。

例如下面的实体及其组件:

Parent(Entity)Child(Entity)
LocalToWorldLocalToWorld
TranslationLocalToParent
RotationParent
Scale

那么transform system会:

  1. [TRSToLocalToWorldSystem] Parent: LocalToWorld = Translation * Rotation * Scale
  2. [TRSToParentSystem] Child: LocalToWorld = LocalToWorld[Parent] * LocalToParent

系统保证父节点实体IDs对应的 LocalToWorld会在子节点实体ID对应的LocalToParent与它相乘前计算储结果。

注:循环的父子关系是无效的,结果未定义。

当拓扑层级改变(例如,任一父节点添加,删除或改变组件)时,SystemStateComponentState内部状态会被添加:

  • 子节点组件(ISystemStateBufferElementData of Entity)关联到父节点实体ID
  • PreviousParent组件(ISystemStateComponentData of Entity)关联到子节点实体ID
Parent(Entity)Child(Entity)
LocalToWorldLocalToWorld
TranslationLocalToParent
RotationParent
ScalePreviousParent*
Child*

添加,删除,更新这些组件,都是由[ParentSystem]完成的。transform systems之外的系统不能读写这些组件。

LocalToParent = Translation * Rotation * Scale

如果任一Translation(float3),Rotation(Quaternion),Scale(float)组件与LocalToParent组件同时出现,transform system 会将这些组件的值组合并写到 LocalToParent中

  • [TRSToLocalToParentSystem] LocalToWorldParent <= Translation
  • [TRSToLocalToParentSystem] LocalToWorldParent <= Translation * Rotaion
  • [TRSToLocalToParentSystem] LocalToWorldParent <= Translation * Rotation * Scale
  • [TRSToLocalToParentSystem] LocalToWorldParent <= Rotation
  • [TRSToLocalToParentSystem] LocalToWorldParent <= Rotation * Scale
  • [TRSToLocalToParentSystem] LocalToWorldParent <= Scale

对于如下父子实体:

Parent(Entity)Child(Entity)
LocalToWorldLocalToWorld
TranslationLocalToParent
RotationParent
ScalePreviousParent*
Child*Translation
Rotation
Scale

那么变换系统会分别执行:

  1. [TRSToLocalToWorldSystem] Parent: LocalToWorld = Translation * Rotation * Scale
  2. [TRSToLocalToParentSystem] Child: LocalToParent = Translation * Rotation * Scale
  3. [LocalToParentSystem] Child: LocalToWorld = LocalToWorld[Parent] * LocalToParent

如果父节点也有父节点:

Parent(Entity)Child(Entity)
LocalToWorldLocalToWorld
LocalToParentLocalToParent
ParentParent
PreviousParent*PreviousParent*
Child*Translation
TranslationRotation
RotationScale
Scale

那么变换系统会执行:

  1. [TRSToLocalToParentSystem] Parent: LocalToParent = Translation * Rotation * Scale
  2. [TRSToLocalToParentSystem] Child: LocalToParent = Translation * Rotation * Scale
  3. [LocalToParentSystem] Parent: LocalToWorld = LocalToWorld[Parent] * LocalToParent
  4. [LocalToParentSystem] Child : LocalToWorld = LocalToWorld[Parent] * LocalToParent

也就是,TRSToLocalToParentSystem先执行,再执行LocalToParentSystem。

Section 3: Default Conversion (Basic)

Hybrid Conversion:

带有UnityEngine.Transform MonoBehaviour 组件的GameObjects,并且有 Covert To Entity MonoBehaviour组件,或者该GameObject在Sub Scenes内,则该GameObject 有默认的转换,将UnityEngine.Transform转换为Transform 系统的组件。该转换可以在 Unity.Transforms.Hybrid 的程序集中的 TransformConversion 系统找到。

要被转换成Enttities的 有静态component的GameObject,仅会为entity添加 LocalToWorld 组件。因此对于静态实例,运行时transform systems不会被更新。

对于非静态的实体,

a. 添加Translation组件并将Transform.position的值赋给它。

b. 添加Rotation组件并将Transform.rotation的值赋给它。

c. 如果Transform.parent == null ,对于非均匀缩放(x,y,z都相等为均匀缩放)的Transform.localScale,添加NonUniformScale组件,并将Transform.localScale的值赋给它。

d. 如果Transform.parent != null,在层级(局部的)开始转换时,对非均匀的Transform.lossyScale,添加NonUniformScale组件,并将Transform.lossyScale的值赋给它。

e. 对于其它Transform.parent != null的情况,添加Parent组件,并引用Transform.parent转换的Entity。添加LocalToParent组件。

Section 4: Non-hierarchical Transforms (Advanced)

NonUniformScale(float3)是一种在每个轴上分别缩放的方式。需要注意的是,不是所有的DOTS特性都完全支持非均匀缩放。确保核实它们的文档来确定它们的限制。

  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * Rotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * Rotation * NonUniformScale
  • [TRSToLocalToWorldSystem] LocalToWorld <= Rotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Rotation * NonUniformScale
  • [TRSToLocalToWorldSystem] LocalToWorld <= NonUniformScale

Scale和NonUniformScale同时存在是无效的情况,但是结果是确定的,Scale将会被应用,NonUniformScale会被忽略。

例如下面的实体组件:

(Entity)
LocalToWorld
Translation
Rotation
NonUniformScale

transform system 将:

[TRSToLocalToWorldSystem] Write LocalToWorld <= Translation * Rotation * NonUniformScale

用户可以直接以四元数的形式写入 Rotation 组件数据。然而如果用的是欧拉角,对应旋转顺序的组件将被添加,并向Rotation组件写入数据。

  • [RotationEulerSystem] Rotation <= RotationEulerXYZ
  • [RotationEulerSystem] Rotation <= RotationEulerXZY
  • [RotationEulerSystem] Rotation <= RotationEulerYXZ
  • [RotationEulerSystem] Rotation <= RotationEulerYZX
  • [RotationEulerSystem] Rotation <= RotationEulerZXY
  • [RotationEulerSystem] Rotation <= RotationEulerZYX

如果是下面的组件布局:

(Entity)
LocalToWorld
Translation
Rotation
RotationEulerXYZ

则 transform system 会:

  1. [RotationEulerSystem] Write Rotation <= RotationEulerXYZ
  2. [TRSToLocalToWorldSystem] Write LocalToWorld <= Translation * Rotation * Scale

如果一个Entity拥有多个RotationEular***的组件,说明该实体创建出错了,但是结果也被定义了。第一优先级顺序的将被应用。优先级顺序为:

  1. RotationEulerXYZ
  2. RotationEulerXZY
  3. RotationEulerYXZ
  4. RotationEulerYZX
  5. RotationEulerZXY
  6. RotationEulerZYX

对于更加复杂的旋转需要,可以使用CompositeRotation(float4x4)。

  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * CompositeRotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * CompositeRotation * Scale
  • [TRSToLocalToWorldSystem] LocalToWorld <= CompositeRotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= CompositeRotation * Scale
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * CompositeRotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= Translation * CompositeRotation * NonUniformScale
  • [TRSToLocalToWorldSystem] LocalToWorld <= CompositeRotation
  • [TRSToLocalToWorldSystem] LocalToWorld <= CompositeRotation * NonUniformScale

可以在代码中直接对CompositeRotation组件赋float4x4的值。然而如果用的是Maya/FBX数据接口,将会添加相关组件并向CompositeRotation中写数据:

CompositeRotation = RotationPivotTranslation * RotationPivot * Rotation * PostRotation * RotationPivot^-1

如果RotationPivotTranslation(float3),RotationPivot(float3),Rotation(quaternion),或PostRotatin(quaternion)和CompositeRotation一起出现,则transform system会将他们组合并写到CompositeRotation中去。

CompositeRotation通常用不到,包括后面的CompositeScale,就不说了,读者自己查阅吧。

Section 5: Hierarchical Transforms (Advanced)

注:高级的层级 transform 组件规则跟non-hierarchical非常类似,除了层级的是写入到LocalToParent。主要增加的专用的组件是ParentScaleInverse。

  • [TRSToLocalToParentSystem] LocalToParent <= Translation
  • [TRSToLocalToParentSystem] LocalToParent <= Translation * Rotation
  • [TRSToLocalToParentSystem] LocalToParent <= Translation * Rotation * NonUniformScale
  • [TRSToLocalToParentSystem] LocalToParent <= Rotation
  • [TRSToLocalToParentSystem] LocalToParent <= Rotation * NonUniformScale
  • [TRSToLocalToParentSystem] LocalToParent <= NonUniformScale

如下父子实体的组件布局:

Parent(Entity)Child(Entity)
LocalToWorldLocalToWorld
TranslationLocalToParent
RotationParent
ScalePreviousParent*
Child*Translation
Rotation
NonUniformScale

transform系统会执行:

  1. [TRSToLocalToWorldSystem] Parent: Write LocalToWorld as defined above in “Non-hierarchical Transforms (Basic)”
  2. [TRSToLocalToParentSystem] Child: Write LocalToParent <= Translation * Rotation * NonUniformScale
  3. [LocalToParentSystem] Child: Write LocalToWorld <= LocalToWorld[Parent] * LocalToParent

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdastLLj-1576920475766)(https://docs.unity3d.com/Packages/com.unity.entities@0.1/manual/images/sec5-1.png)]

更高级的不翻了

Section 6: Custom Transforms (Advanced)

有2种方法来写用户定义的transforms来与transform system兼容:

  1. 覆写transform components
  2. 扩展transform components

Overriding transform components

添加一个用户自定义的组件,并添加LocalToWorld WriteGroup:

[Serializable]
[WriteGroup(typeof(LocalToWorld))]
struct UserComponent : IComponentData
{
}

覆写意味着添加了UserComponent后,由用户来完成完成转换。

在UserTransformSystem中,使用默认的query方法来访问LocalToWorld,例如:

public class UserTransformSystem : Jobcomponent
{
	[BurstCompile]
	struct UserTransform : IJobForEach<LocalToWorld, UserComponent>
	{
		public void Execute(ref LocalToWorld localToWorld, [ReadOnly] ref UserComponent userComponent)
		{
			localToWorld.Value = ... // 根据需要对localToWorld进行赋值
		}
	}

	protected override JobHandle OnUpdate(JobHandle inputDependencies)
	{
		var job = new UserTransform()
		{
		};
		return job.Schedule(this, inputDependencies);
	}
}

对添加了UserComponent的实体,所有其它写LocalToWorld 的transform 组件都将被忽略。

例如以下实体组件布局:

(Entity)
LocalToWorld
Translation
Rotation
Scale
UserComponent

那么:

[TRSToLocalToWorldSystem] 不执行

[UserTransformSystem] 执行

如果有2个覆写LocalToWorld的组件存在:

UserComponent2

UserTransformSystem2

两个系统都会写LocalToWorld,将导致未知结果。

Extending transform components

为了保证多个组件正确地转换,需要定义WriteGroup过滤:

[Serializable]
[WriteGroup(typeof(LocalToWorld))]
struct UserComponent : IComponentData
{
}

基于WriteGroup的过滤:

public class UserTransformSystem : JobComponentSystem
{
	private EntityQuery m_Query;
	
	protected override void OnCreate()
	{
		m_Query = GetEntityQuery(new EntityQueryDesc()
		{
			All = new ComponentType[]
			{
				ComponentType.ReadWrite<LocalToWorld>(),
				ComponentType.ReadOnly<userComponent>(),
			},
			Options = EntityQueryDescOption.FilterWriteGroup
		}
		);
	}
	
	    [BurstCompile]
    struct UserTransform : IJobForEach<LocalToWorld, UserComponent>
    {
        public void Execute(ref LocalToWorld localToWorld, [ReadOnly] ref UserComponent userComponent)
        {
            localToWorld.Value = ... // Assign localToWorld as needed for UserTransform
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new UserTransform()
        {
        };
        return job.ScheduleGroup(m_Query, inputDependencies);
    }
}

UserTransformSystem的m_Query只处理明确提到的组件。

例如,下面的实体将被处理:

(Entity)
LocalToWorld
UserComponent

下面的不会被处理:

(Entity)
LocalToWorld
Translation
Rotation
Scale
UserComponent

包含了UserComponent的实体,如果包含其它在同一个WriteGroup的组件,并且没有明确声明的组件,实体将不处理

上面的实体也可以定义EntityQuery来处理:

public class UserTransformExtensionSystem : JobComponentSystem
{
    private EntityQuery m_Query;

    protected override void OnCreate()
    {
        m_Query = GetEntityQuery(new EntityQueryDesc()
        {
            All = new ComponentType[]
            {
                ComponentType.ReadWrite<LocalToWorld>(),
                ComponentType.ReadOnly<UserComponent>(),
                ComponentType.ReadOnly<Translation>(),
                ComponentType.ReadOnly<Rotation>(),
                ComponentType.ReadOnly<Scale>(),
            },
            Options = EntityQueryDescOptions.FilterWriteGroup
        });
    }

    [BurstCompile]
    struct UserTransform : IJobForEach<LocalToWorld, UserComponent>
    {
        public void Execute(ref LocalToWorld localToWorld, [ReadOnly] ref UserComponent userComponent,
            [ReadOnly] ref Translation translation,
            [ReadOnly] ref Rotation rotation,
            [ReadOnly] ref Scale scale)
        {
            localToWorld.Value = ... // Assign localToWorld as needed for UserTransform
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new UserTransform()
        {
        };
        return job.ScheduleGroup(m_Query, inputDependencies);
    }
}

同样,我们在加一个组件:

[Serializable]
[WriteGroup(typeof(LocalToWorld))]
struct UserComponent2 : IComponentData
{
}

实体布局:

(Entity)
LocalToWorld
UserComponent
UserComponent2

我们可以定义相应的EntityQuery来处理:

public class UserTransformComboSystem : JobComponentSystem
{
    private EntityQuery m_Query;

    protected override void OnCreate()
    {
        m_Query = GetEntityQuery(new EntityQueryDesc()
        {
            All = new ComponentType[]
            {
                ComponentType.ReadWrite<LocalToWorld>(),
                ComponentType.ReadOnly<UserComponent>(),
                ComponentType.ReadOnly<UserComponent2>(),
            },
            Options = EntityQueryDescOptions.FilterWriteGroup
        });
    }

    [BurstCompile]
    struct UserTransform : IJobForEach<LocalToWorld, UserComponent>
    {
        public void Execute(ref LocalToWorld localToWorld, 
            [ReadOnly] ref UserComponent userComponent,
            [ReadOnly] ref UserComponent2 userComponent2
        {
            localToWorld.Value = ... // Assign localToWorld as needed for UserTransform
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new UserTransform()
        {
        };
        return job.ScheduleGroup(m_Query, inputDependencies);
    }
}

翻译到此结束,历时将近2个月,中间经历了Word文档文件损坏,又改用Markdown,。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值