Magica Cloth

Magica Cloth

Magica Cloth当前在物理模拟的效果上相比Dynamic Bone插件更加强大,但是随之而来的性能消耗也不容忽视。本文旨在说明通过简单修改Magica Cloth插件中的部分设置项以及调整一些参数,就能够在它的表现效果和性能之间达到一个平衡。

一、Magica Cloth简介

Magica Cloth是一款用于Unity引擎中布料模拟的插件,提供了类似于Unity的Cloth组件的功能。由于效果逼真的布料模拟运算量巨大,耗时较长,每一帧可能需要几十秒的时间来进行模拟;而在游戏中实现的布料模拟因为需要满足帧率的要求,要在几毫秒的时间内进行物理解算才不至于影响其他系统。因此,游戏中的布料模拟往往是极度简化的,以满足实时性的要求。


图片来源Magica Cloth – Magica Soft

二、Magica Cloth使用

关于Magica Cloth的使用,可以参考官方文档,这里就不再赘述。
https://magicasoft.jp/en/magica-cloth-2

三、Magica Cloth的具体效果

Magica Cloth能够让角色的衣服、头发等受到重力、风等外部作用力的影响,表现出比较自然的运动;能够让衣服、头发在角色运动的时候随着角色的动作比较真实地改变自己的位置和形状。

Magica Cloth提供了四种基本的物理组件:Bone Cloth,Bone Spring,Mesh Cloth以及Mesh Spring。

  • Bone Cloth可以用来模拟有骨骼的头发的晃动,负载比较轻。
  • Bone Spring可以用来模拟有骨骼的胸部的简谐运动,负载最轻。
  • Mesh Cloth基于所选的网格的顶点来进行布料模拟,效果最好但是负载最重。
  • Mesh Spring可以用来模拟没有骨骼的胸部的简谐运动,负载中等。

和Dynamic Bone相比,Magica Cloth无疑功能更加强大,但是如果只是需要移动角色骨骼来实现物理模拟,Dynamic Bone也能做到比较好的结果并且性能更优。

同时,Magica Cloth相比其他物理插件功能更强大的地方在于它有相对而言比较容易调试的防穿模手段。

Magica Cloth还提供了换装系统,可以在运行时更换角色身上的服装,但是这些服装都是要在离线时做好预设,不能够在运行时修改。

四、Magica Cloth的实现原理

首先,它不依赖于Unity的物理系统,所以Unity自带的物理相关的设置不会影响Magica Cloth的表现。Magica Cloth通过Magica Physics Manager从而在运行时控制物理模拟的全部流程,所以需要在场景中存在这个Manager的时候物理模拟才会生效。

其次,它的实时运算使用了Unity的Job System和Burst Compile,能够把一些复杂的布料模拟的计算转移到工作线程中,降低了主线程的CPU占用。

当我们要使用Mesh Cloth或者Mesh Spring的时候,就需要添加额外的Render Deformer和Virtual Deformer。Render Deformer对模型的Mesh进行变形,而Virtual Deformer则是把所有的Render Deformer整合成一个虚拟Mesh并进行简化,也就是合并顶点和三角形,减少顶点和三角形的数量,降低数据量,然后把这个虚拟的Mesh对应的简化结果分配给各个Render Deformer用来进行Mesh的控制和变形。

为了能够尽可能地解决穿模问题,Magica Cloth可以针对性地设置Surface Penetration和Collider Penetration。其中,Surface Penetration在身体骨骼对裙摆等布料的运动产生影响时会比较有效,而当身体骨骼不会影响服饰的运动时,就需要使用Collider Penetration来手动设置碰撞体。Magica Cloth提供了三种Collider:Sphere、Capsule和Plane。因为主要是用于人物角色的Collider,所以常用的是Sphere和Capsule。对于部分布料模拟中分离的骨骼也可以通过设置Connection Mode进行合并,来防止运动时附加了碰撞体的躯干、大腿等从布料模拟骨骼的间隙中穿出去。

同时,除了防穿模设置外,也可以通过限制骨骼的最大位置和旋转偏移来尽可能限制骨骼节点可能的位置,这也有助于减少穿模现象的发生。

五、Magica Cloth的性能表现

在实际应用中可以发现,Magica Cloth虽然使用了Job System和Burst Compile的方法来进行物理计算,但是它造成的CPU耗时依然较高,官方文档中也提到Magica Cloth组件的耗时是稍高于Unity自身提供的Cloth组件的。

但是,Magica Cloth的优点也有很多。它使用方便:针对角色各个部位的布料模拟提供了很多的预设,例如头发的预设就包括刘海、长发、短发以及马尾等不同表现的参数,制作人员可以依据表现效果进行参数的微调,简化了参数调节的过程;为了防止穿模,它提供了一些参数用于限制骨骼节点的最大位移和旋转,同时也设计了适合于游戏中角色的常见的碰撞体,并给出了防穿模的算法。

相比于可以实现类似物理模拟的Dynamica Bone插件,Magica Cloth的缺点还是在于它较重的CPU负载。同样是对Unity-Chan头发的运动进行模拟,Magica Cloth在使用Bone Cloth组件时在整个测试过程中CPU平均占用为0.96ms,而Dynamic Bone做相同的工作只需要0.24ms。(以下测试案例使用机型均为小米8)

Magica Physica的优点则是在于提供了更加丰富的物理效果。Magica Cloth不仅能够控制角色的骨骼节点,对于角色的网格,也可以通过网格的变形来模拟出一定的物理效果,比如说能够控制角色身上的各种服饰随着角色的运动的变形,得出比较好的物理效果。

六、Magica Cloth的性能优化

为了对项目中使用的Magica Cloth组件进行针对性的优化,首先需要了解Magica Cloth的性能消耗的主要来源。为此我们同样是使用Unity-Chan的模型进行该插件的性能消耗的分析。

在官方文档中提到,Magica Cloth依赖Job System和Burst Compile进行物理计算,会在游戏运行过程中使用工作线程来完成大部分的物理模拟计算,而通过GOT Online对测试案例进行分析后可以发现,Magica Cloth插件的主要CPU耗时都会被统计到MagicaPhysicsManager这个函数下。下面将结合该函数的源代码和测试结果进行分析。

1. Magica Physics Manager工作流程
在该函数的Update中主要是检查自定义的GameLoop是否在正常工作,如果自定义的GameLoop没有生效,就执行它的初始化操作。

        private void Update()
        {
            // 对于 Unity2019.3 或更高版本,请在更新期间检查一次自定义循环注册
            // 已注册则通过,未注册则重新注册
            // 这是在 PlayerLoop 被其他资产重写的情况下的对策
            if (updatePlayerLoop == false)
            {
                //Debug.Log("Update check!!"); 
                InitCustomGameLoop();
                updatePlayerLoop = true;
            }
        }

在自定义的GameLoop中注册了afterEarlyUpdate、afterFixedUpdate、afterUpdate、afterLateUpdate、postLateUpdate以及afterRendering这几个回调。在这些回调中,主要是通过使用PhysicsManagerCompute类的方法来安排各种物理计算的Job,这些Job也是Magica Cloth插件最主要的耗时来源。

继续查看Compute中各个Job的调度,可以发现,绝大部分Job需要执行的总任务数和Particle的数量相关。

jobHandle = job.Schedule(Particle.Length, 64, jobHandle);

Particle是Magica Cloth中用来表征网格顶点或者骨骼节点的数据结构,在离线生成序列化数据时,可以为网格顶点或者骨骼节点设置Move、Fixed和Invalid三种标记,其中只有被标记为Invalid的网格顶点或者骨骼节点才不会参与物理计算,也就是说不会对上面所说的Particle.Length产生影响。因此,在制作过程中尽可能地减少Move和Fixed节点是最有效的降低物理模拟耗时的方法,当然,这样做也可能会带来一定的表现效果上的损失,需要根据实际情况来决定到底哪些节点需要进行物理模拟。

2. Delay Unscaled Time的使用与否

在Magica Physics Manager的Update选项下,根据Update Mode的选择是否使用了Delay Unscaled Time,Compute的计算方式有所不同。

  • 不使用Delay Unscaled Time时
            if (useDelay == false)
            {
                // 即时
                OnPreUpdate.Invoke();
                Compute.UpdateTeamAlways();
                Compute.InitJob();
                Compute.UpdateReadBone();
                Compute.UpdateStartSimulation(updateTime);
                Compute.UpdateWriteBone();
                Compute.UpdateCompleteSimulation();
                Compute.UpdateWriteMesh();
                OnPostUpdate.Invoke();
            }
  • 使用了Delay Unscaled Time后
            if (useDelay)
            {
                // 延迟执行
                OnPreUpdate.Invoke();
                Compute.UpdateTeamAlways();
                Compute.InitJob();
                Compute.UpdateReadWriteBone();
                Compute.UpdateStartSimulation(updateTime);
                Compute.ScheduleJob();
                Compute.UpdateWriteMesh(); // 将先前的结果应用于网格
                //Debug.Log($"Delay Job! F:{Time.frameCount}");
            }

不同的计算方式也导致了最终表现上的差异。使用Delay Unscaled Time后,因为计算的完成发生在渲染之后也就是在afterRendering回调中,所以实际的物理模拟的结果会比画面慢一帧。为了解决画面和物理不同步的问题,Magica Cloth通过上一帧和当前帧的物理模拟结果进行插值,从而得到一个预测的结果同步给下一帧。

// 未来预测
                        float ratio = unityPhysics ? fixedFutureRatio : normalFutureRatio; // 因骨骼更新模式而异                        pos = math.lerp(oldPos, pos, 1.0f + ratio * moveRatio);
                        rot = math.slerp(oldRot, rot, 1.0f + ratio * angRatio);
                        rot = math.normalize(rot);
                        bonePosList[index] = pos;
                        boneRotList[index] = rot;
                        // 记录未来的预测
                        futurePosList[index] = pos;
                        futureRotList[index] = rot;

显然,这样做会导致在碰撞检测等物理效果上出现一定的问题,但是由于它在性能消耗上更加优秀,所以可以说是非常重要的优化手段。如果对画面表现和物理结果的同步性和防穿模的要求并不是非常高,完全可以选择延迟更新的模式。在测试案例中,我们对同一个场景使用Delay Unscaled Time更新模式和非Delay更新模式进行了对比,结果表明Delay Unscaled Time更新模式下,插件的耗时大幅度降低。

  • Delay Unscaled Time更新模式

  • 非Delay更新模式

3. Merge Vertex Distance的选取

在使用Mesh Cloth或者Mesh Spring时,在Virtual Deformer组件中可以调节Merge Vertex Distance,该距离越大,生成的虚拟网格的Partcile数量就会越少,从而在物理模拟时需要的计算量就越小。这种优化的思路仍然是尽可能减少参与物理计算的顶点的数量,从而降低耗时。测试结果也表明,改变这个值可以有效降低插件的CPU性能消耗。

  • Merge Vertex Distance取0.001时

  • Merge Vertex Distance取0.01时

  • Merge Vertex Distance取0.1时

4. Use Skinning的使用与否

同样是在Virtual Deformer组件上需要关注的是Use Skinning选项是否需要勾选。

            if (use && IsSkinning && IsChangeBoneWeights && vertexCalc)
            {
                // 顶点权重变化
                //Debug.Log("Change Mesh Weights:" + mesh.name + " buff:" + bufferIndex + " frame:" + Time.frameCount);
                MagicaPhysicsManager.Instance.Mesh.CopyToRenderMeshBoneWeightData(MeshIndex, mesh, sharedMesh, bufferIndex);
                IsChangeBoneWeights = false;
            }

如果勾选了Use Skinning,则需要对所有顶点的骨骼权重进行重新赋值,在官方文档中也提到,取消Use Skinning可以极大地减少物理计算的负担。它的限制在于当取消了Use Skinning选项后,网格只会受到影响程度最大的Virtual Deformer的影响,可能在一定程度上会影响最终的表现,但是如果对于一个网格的Render Deformer只在一个Virtual Deformer中使用的情况下则可以放心地取消该选项的勾选。勾选和取消勾选的测试结果如下。

  • 勾选Use Skinning

  • 取消勾选Use Skinning

5. Normal And Tangent Update Mode的选择

在使用Mesh Cloth或者Mesh Spring时同样需要关注的是Normal And Tangent Update Mode的选择。

        public enum RecalculateMode
        {
            None = 0,
            // 每帧更新法线
            UpdateNormalPerFrame = 1,
            // 每帧更新法线和切线
            UpdateNormalAndTangentPerFrame = 2,
        }

如果在渲染中并不要求持续更新网格的切线或者法线数据,可以把该选项设置为None,减少不必要的计算。

            if ((use || IsChangePosition || IsChangeNormalTangent) && vertexCalc)
            {
                // 重写Mesh
                // 将meshBuffer设定为mesh(工作量巨大)
                // ★目前为止除此以外别无他法,经过思考有2个可以避开的办法:
                // ★(1)等待未来版本的Unity上进行支持
                // ★(2)使用computeBuffer对Shader顶点进行合批(很快,但是因为必须使用特殊Shader所以兼容性较低)
                MagicaPhysicsManager.Instance.Mesh.CopyToRenderMeshLocalPositionData(MeshIndex, mesh, bufferIndex);
                bool normal = normalAndTangentUpdateMode == RecalculateMode.UpdateNormalPerFrame || normalAndTangentUpdateMode == RecalculateMode.UpdateNormalAndTangentPerFrame;
                bool tangent = normalAndTangentUpdateMode == RecalculateMode.UpdateNormalAndTangentPerFrame;
                if (normal || tangent)
                {
                    MagicaPhysicsManager.Instance.Mesh.CopyToRenderMeshLocalNormalTangentData(MeshIndex, mesh, bufferIndex, normal, tangent);
                }
                else if (IsChangeNormalTangent)
                {
                    // 返回
                    mesh.normals = sharedMesh.normals;
                    mesh.tangents = sharedMesh.tangents;
                }
                IsChangePosition = false;
                IsChangeNormalTangent = false;
            }

三种选项下的测试结果如下所示:

  • None

  • UpdateNormalPerFrame

  • UpdateNormalAndTangentPerFrame

七、Magica Cloth总结

Magica Cloth当前在物理模拟的效果上相比Dynamic Bone插件更加强大,但是随之而来的性能消耗也不容忽视。本文旨在说明通过简单修改Magica Cloth插件中的部分设置项以及调整一些参数,就能够在它的表现效果和性能之间达到一个平衡。

除了上文提到的一些性能瓶颈之外,如果在项目中使用了Magica Cloth提供的Collider Penetration,以及设置了各种位置和旋转的限制时也会导致一定的性能消耗,但它们的影响不及上一节中提到的几个重要的优化点。建议在对Magica Cloth进行性能优化时先尝试通过上述的办法进行修改,再去关注穿模和位置旋转限制等相关设置。