学习Unity 2019 ECS框架(二)

ECS很是适合用于大规模物件的动画交互,好比这个流体模拟https://connect.unity.com/p/shi-yong-unityde-ecshe-job-systemshi-xian-liu-ti-mo-ni-xiao-guogit

他在github里给出了传统SPH实现(MonoBehaviour)的源码,和使用ECS架构后的源码。github

 

先解析下传统单线程实现,也就是MonoBehaviour。数组

大致思路是在每一个粒子的MonoBehaviour里,计算本身和其余粒子在必定密度下受到的力,相互做用力产生的速度与运动方向,再应用到坐标位置上。架构

 

private void Start() { InitSPH(); } private void Update() {
  // 计算密度压力 ComputeDensityPressure();
  // 计算力(含方向) ComputeForces();
  // 计算位置 Integrate();
  // 计算碰撞 ComputeColliders();   // 应用位置 ApplyPosition(); }

 

计算粒子间的流体碰撞共使用到下列参数,SPH包括粒子密度渗透。框架

其中restDensity和smoothingRadius是粒子间超过必定距离上的阈值,则不在计算相互做用力,这也符协力学运动物质趋于稳定的物理学规律。dom

[System.Serializable] private struct SPHParameters { public float particleRadius;    // 粒子半径
    public float smoothingRadius;   // 平滑半径
    public float smoothingRadiusSq; // 平衡半径开方
    public float restDensity;       // 休息密度
    public float gravityMult;       // 重力加速
    public float particleMass;      // 质点 
    public float particleViscosity; // 颗粒粘度
    public float particleDrag;      // 粒子牵引
    #pragma warning restore 0649 }

 

InitSPH()ide

初始化粒子的位置,将位置摆放为x * y * z,添加必定的位置扰动。oop

// 计算抖动:增长必定随机性,将随机值的值域映射到【-1,1】,将抖动缩小到0.1动画

float jitter = (Random.value * 2f - 1f) * parameters[parameterID].particleRadius * 0.1f;this

粒子位置摆放x z方向都加上了Random.Range(-0.1f, 0.1f)的随机值,这个随机值和扰动都不用太大,只是给初始位置增长一点移动,避免运行后的流体只是单纯下落。

 

ComputeDensityPressure()

计算相互间的做用里,因此须要两个for,进行O(n^2)的遍历计算

Vector3 rij = particles[j].position - particles[i].position; // 指向j的方向向量,是i粒子对j粒子的做用力

// 若是以前距离小于平滑半径,则须要进行密度计算
if
(r2 < parameters[particles[i].parameterID].smoothingRadiusSq) { // 质点 * 圆周的一些参数 * pow(平滑半径,9) * pow(平滑半径距离 - 实际距离, 3) particles[i].density += parameters[particles[i].parameterID].particleMass * (315.0f / (64.0f * Mathf.PI * Mathf.Pow(parameters[particles[i].parameterID].smoothingRadius, 9.0f))) * Mathf.Pow(parameters[particles[i].parameterID].smoothingRadiusSq - r2, 3.0f); }

计算并存储粒子受到压力particles[i].pressure,压力值与粒子间密度有关。

 

计算做用力与速度,由于是受全部粒子的做用力,是个累加值,速度一样的道理。下面的代码稍微简化了下,不是原代码。

// 小于平滑阈值,则计算相互做用力(压力)
if (r < parameters[particles[i].parameterID].smoothingRadius) { // -rij 指向本身 // 计算本身受到的压力值,计算压力的粒子距离减去平滑半径,也便是不受力的距离
    forcePressure += -rij.normalized * particleMass * (particles[i].pressure + particles[j].pressure) / (2.0f * particles[j].density) * (-45.0f / smoothingRadius, 6.0f))) * smoothingRadius - r, 2.0f); forceViscosity += particleViscosity * particleMass * (particles[j].velocity - particles[i].velocity) / particles[j].density * (45.0f / smoothingRadius, 6.0f))) * (smoothingRadius - r); }

 

ComputeColliders()

这个操做我想是计算与地面/墙壁的碰撞,对于其余碰撞体,都加上SPHCollider标签,for循环计算particles数组内每一个粒子和GameObject.FindGameObjectsWithTag("SPHCollider")场景内全部SPHCollider标签的物体进行‘碰撞检测’,大体实现是:在粒子球体半径范围内,经过叉积计算碰撞面的法线方向,而后在地面/墙面的投影计算渗透长度与位置?没看懂。

 

最后计算一堆点积累加计算各个方向的力,应用到粒子位置上。

 

 

JobSystem & Unity ECS

1. SPHCollider : IComponentData

2. SPHParticle : ISharedComponentData

3. SPHVelocity : IComponentData

先使用ComponentData接口实现数据,这在Unity ECS中被归为Component组件,尽管在传统MVC被认为是Model,但这里和Component联系更紧密,就像GameObject挂载Component也有一堆Serializable的字段同样。

 

SPHManager : MonoBehaviour

它构建了整个场景,给墙壁/地面添加Collider,排列粒子位置,感受有点像World的一部分。

private void Start() { // Imoprt //manager = World.Active.GetOrCreateSystem<EntityManager>();
        manager = World.Active.EntityManager; // Setup
 AddColliders(); AddParticles(amount); }

 

SPHSystem : JobComponentSystem

JobHandle OnUpdate(JobHandle inputDeps)每帧调用里,处理了各个IComponentData & IJobParallelFor,IJobParallelFor只定义了Execute处理每帧当前数据须要作的/执行的操做,属于行为,行为与Component解耦,虽然本来也没在一块儿,但若是说MonoBehaviour里处理了行为叫作Component的行为,好吧。

总之按顺序new 这些实现了IJobParallelFor的结构体,Unity内部会去注册这些Execute方法并分布运行,咱们只须要保证这些接口实现的调用时正确顺序就行。

[BurstCompile]
private struct ComputeForces : IJobParallelFor // 行为


[JobProducerType(typeof(IJobParallelForExtensions.ParallelForJobStruct<>))] public interface IJobParallelFor { // // 摘要: // Implement this method to perform work against a specific iteration index. // // 参数: // index: // The index of the Parallel for loop at which to perform work. void Execute(int index); }

 

 

这样看来Unity ECS的使用并不会比MonoBehaviour更加复杂,咱们只须要掌握几个概念ComponentData, World, ComponentSystem,以及实现接口和数据正确,剩下的就交给框架。