水の軽量化

Nvidia Flexの水をカスタマイズしたときの最適化に関するメモ

z軸固定

全パーティクルについてfor分を回して、GetVelocity SetVelocityは重い。
全パーティクルは3000程度いくのできっつい。
新しくループを作るのではなく、もとのNvidiaを書き換える事で対応。
→ 書き換えたのは、新規に生成された水粒子に関してのみだったのでダメだった。
新規の水粒子以外でSetVelocityが呼ばれているのは、ApplyImpulseTeleportParticlesだが、DebugLogしてもどちらも呼び出されていないようだった。

しかし、CustomWater.cs内のOnFlexUpdateGetVelocityするとzの値が0ではなくなっている。
つまりSetVelocity以外の部分でVelocityの変化が起きていることになる。
原因を探ったがよく分からず(ネイティブプラグイン内かも)、その過程でSetVelocities関数を発見した。
SetVelocityをfor分で回すより効率が良いと考え、利用してみた。

var velocities = new Vector3[indices.Length];
particleData.GetVelocities(indices[0], indices.Length, velocities);
for(var i = 0; i < velocities.Length; i++)
    velocities[i].z = 0;
particleData.SetVelocities(indices[0], indices.Length, velocities);

結果、ほぼ無視できるレベルに軽量化に成功した。
が、代わりにとんでもないGCが発生した。
原因はGetVelocitiesの時のnew Vector3[]だろう。
f:id:tmls:20200917064018p:plain

ちなみに、SetVelocityを使った場合との比較は↓である。
f:id:tmls:20200917064513p:plain

手法 Total GC
SetVelocity 2.90ms (7.6%) 29.5kB
SetVelocities 0.03ms (0.1%) 約60kB (スパイク時:117.7kB)

SetVelocitiesを使う場合はindicesはスタートだけあればよいので、 int[] indices = new int[instance.numParticles];int[] indices = new int[1]; に変える事でnewを減らすことに成功。
GCを60kB → 44kB (スパイク時:88kB)まで減らした。

ここでのスパイクはFixedUpdateが1フレーム内で2回呼ばれた時の話だと考えられる。

f:id:tmls:20200917071059p:plain

さらに、_velocitiesをクラス変数にして初期化する事でnewを呼ぶ回数は最初の初期化時に限定。
結果、GCを44kB → 0.072kB (72B) に減らすことに成功!

private Vector3[] _velocities;

private void Start(){
    _velocities = new Vector3[_flexActor.asset.maxParticles];
}

private void OnFlexUpdate(FlexContainer.ParticleData particleData){
    particleData.GetVelocities(indices[0], instance.numParticles, _velocities);
        for(var i = 0; i < _velocities.Length; i++)
            _velocities[i].z = 0;
        particleData.SetVelocities(indices[0], instance.numParticles, _velocities);    
}

f:id:tmls:20200917072339p:plain

これにて、Z軸固定は完了した。

当たり判定及びAddForce

上記同様に、GetVelocities 及び GetParticlesによる軽量化を行った。

さらに、IsInCollider(Vector3 pos) 内の targetCollider.ClosestPoint(pos)が非常に重い。 これは水粒子がコライダー内部にあるかの判定に利用していた。

そこで、コライダーをSphereColliderに限定して、水粒子のコライダー内部判定を(particle - colPos).magnitude < radiusで置き換えた。
汎用性は下がるが、単純なVector3の演算のみになる。 実際にこれでかなりの軽量化に成功した。

しかしながら、magnitudeの計算等でそれなりに処理時間を要していた。 そこで今回はz軸方向を利用しないという点を活かして、Vector3の演算からVector2の演算に変更を行った。 これでさらに若干軽くなった。

f:id:tmls:20200918061952p:plain

f:id:tmls:20200918063709p:plain

手法 Total GC
WaterCollider 22.50ms (37.0%) 67.4kB
WaterSphereCollider 1.70ms (6.6%) 36B

f:id:tmls:20200918064001p:plain

f:id:tmls:20200918065414p:plain f:id:tmls:20200918065532p:plain

f:id:tmls:20200918070035p:plain f:id:tmls:20200918070050p:plain

f:id:tmls:20200918070843p:plain