學習Unity 2019 ECS框架(二)

ECS非常適合用於大規模物件的動畫交互,比如這個流體模擬https://connect.unity.com/p/shi-yong-unityde-ecshe-job-systemshi-xian-liu-ti-mo-ni-xiao-guo

他在github裏給出了傳統SPH實現(MonoBehaviour)的源碼,和使用ECS架構後的源碼。

 

先解析下傳統單線程實現,也就是MonoBehaviour。

大體思路是在每個粒子的MonoBehaviour裏,計算自己和其他粒子在一定密度下受到的力,相互作用力產生的速度與運動方向,再應用到座標位置上。

 

private void Start()
{
    InitSPH();
}

private void Update()
{
  // 計算密度壓力 ComputeDensityPressure();
  // 計算力(含方向) ComputeForces();
  // 計算位置 Integrate();
  // 計算碰撞 ComputeColliders();   // 應用位置 ApplyPosition(); }

 

計算粒子間的流體碰撞共使用到下列參數,SPH包括粒子密度滲透。

其中restDensity和smoothingRadius是粒子間超過一定距離上的閾值,則不在計算相互作用力,這也符合力學運動物質趨於穩定的物理學規律。

[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()

初始化粒子的位置,將位置擺放爲x * y * z,添加一定的位置擾動。

// 計算抖動:增加一定隨機性,將隨機值的值域映射到【-1,1】,將抖動縮小到0.1

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

粒子位置擺放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,以及實現接口和數據正確,剩下的就交給框架。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章