Unity 性能优化之DynamicBone

DynmaicBone最新版本使用了多线程,30人同屏情况下消耗为6ms左右,如下图:

这个消耗依旧挺大,会使50帧的游戏降10帧左右。

 

使用 job system + burst 优化后的消耗为 0.05 ms,如下图:

 

 优化方案参考如下:

https://blog.csdn.net/tangyin025/article/details/109683573

https://zhuanlan.zhihu.com/p/113367281

本人已将其整合到项目中,提供简略版的DynmaicBone,即DynamicBoneFast,源码分享如下:

DynamicBoneFast.cs

  1 using Unity.Collections;
  2 using Unity.Mathematics;
  3 using UnityEngine;
  4 
  5 [AddComponentMenu("Dynamic Bone/Dynamic Bone Fast")]
  6 public class DynamicBoneFast : MonoBehaviour
  7 {
  8     #region Particle & HeadInfo
  9 
 10     public struct Particle
 11     {
 12         public int m_ParentIndex;
 13         public int m_ChildCount;
 14         public float m_Damping;
 15         public float m_Elasticity;
 16         public float m_Stiffness;
 17         public float m_Inert;
 18 
 19         public float3 m_EndOffset;
 20         public float3 m_InitLocalPosition;
 21         public quaternion m_InitLocalRotation;
 22 
 23         // [ta]
 24         public int index;
 25         public float3 tmpWorldPosition;
 26         public float3 tmpPrevWorldPosition;
 27         public float3 localPosition;
 28         public quaternion localRotation;
 29         public float3 parentScale;
 30         public int isRootParticle;
 31 
 32         //for output
 33         public float3 worldPosition;
 34         public quaternion worldRotation;
 35     }
 36 
 37     // [ta]
 38     public struct HeadInfo
 39     {
 40         int m_HeadIndex;
 41 
 42         public float m_UpdateRate;
 43         public Vector3 m_ObjectMove;
 44         public int m_particleCount;
 45         public int m_jobDataOffset;
 46         public int m_ParticleLoopCount;
 47 
 48         public float3 m_RootParentBoneWorldPos;
 49         public quaternion m_RootParentBoneWorldRot;
 50 
 51         public int HeadIndex
 52         {
 53             get => m_HeadIndex;
 54             set => m_HeadIndex = value;
 55         }
 56     }
 57 
 58     #endregion
 59 
 60     public const int MAX_TRANSFORM_LIMIT = 10;
 61 
 62     public Transform m_Root;
 63     public float m_UpdateRate = 60.0f;
 64     [Range(0, 1)]
 65     public float m_Damping = 0.1f;
 66     [Range(0, 1)]
 67     public float m_Elasticity = 0.1f;
 68     [Range(0, 1)]
 69     public float m_Stiffness = 0.1f;
 70     [Range(0, 1)]
 71     public float m_Inert = 0;
 72 
 73     public NativeArray<Particle> m_Particles;
 74     public Transform[] m_particleTransformArr;
 75     private int m_ParticleCount;
 76     public Transform m_rootParentTransform;
 77     public HeadInfo m_headInfo;
 78     bool m_IsInited;
 79 
 80 
 81     public HeadInfo ResetHeadIndexAndDataOffset(int headIndex)
 82     {
 83         m_headInfo.HeadIndex = headIndex;
 84         m_headInfo.m_jobDataOffset = headIndex * MAX_TRANSFORM_LIMIT;
 85 
 86         return m_headInfo;
 87     }
 88 
 89     public void ClearJobData()
 90     {
 91         if (m_Particles.IsCreated)
 92         {
 93             m_Particles.Dispose();
 94         }
 95 
 96         m_particleTransformArr = null;
 97         m_IsInited = false;
 98     }
 99 
100     void Init()
101     {
102         if (m_Root == null)
103             return;
104 
105         m_headInfo = new HeadInfo();
106         m_headInfo.m_UpdateRate = this.m_UpdateRate;
107         m_headInfo.m_ObjectMove = Vector3.zero;
108         m_headInfo.m_particleCount = 0;
109 
110         m_Particles = new NativeArray<Particle>(MAX_TRANSFORM_LIMIT, Allocator.Persistent);
111         m_particleTransformArr = new Transform[MAX_TRANSFORM_LIMIT];
112         m_ParticleCount = 0;
113 
114         SetupParticles();
115 
116         m_IsInited = true;
117     }
118 
119     void Update()
120     {
121         if (!m_IsInited && m_Root)
122         {
123             Init();
124             DynamicBoneFastMgr.Instance.OnEnter(this);
125         }
126     }
127 
128     void OnDestroy()
129     {
130         m_Root = null;
131         DynamicBoneFastMgr.Instance.OnExit(this);
132     }
133 
134     void SetupParticles()
135     {
136         AppendParticles(m_Root, -1);
137         UpdateParameters();
138 
139         m_headInfo.m_particleCount = m_ParticleCount;
140         m_rootParentTransform = m_Root.parent;
141     }
142 
143     void AppendParticles(Transform b, int parentIndex)
144     {
145         var p = new Particle();
146         p.index = m_ParticleCount++;
147         p.m_ParentIndex = parentIndex;
148 
149         if (b != null)
150         {
151             p.tmpWorldPosition = p.tmpPrevWorldPosition = b.position;
152             p.m_InitLocalPosition = b.localPosition;
153             p.m_InitLocalRotation = b.localRotation;
154 
155             // [ta]
156             p.localPosition = b.localPosition;
157             p.localRotation = b.localRotation;
158             p.parentScale = b.parent.lossyScale;
159             p.isRootParticle = parentIndex == -1 ? 1 : 0;
160         }
161         else     // end bone
162         {
163             Transform pb = m_particleTransformArr[parentIndex];
164             p.m_EndOffset = pb.InverseTransformPoint(transform.position + pb.position);
165             p.tmpWorldPosition = p.tmpPrevWorldPosition = pb.TransformPoint(p.m_EndOffset);
166             p.m_InitLocalPosition = Vector3.zero;
167             p.m_InitLocalRotation = Quaternion.identity;
168         }
169 
170         if (parentIndex >= 0)
171         {
172             ++p.m_ChildCount;
173         }
174 
175         m_Particles[p.index] = p;
176         m_particleTransformArr[p.index] = b;
177 
178         int index = p.index;
179 
180         if (b != null)
181         {
182             for (int i = 0; i < b.childCount; ++i)
183             {
184                 Transform child = b.GetChild(i);
185                 AppendParticles(child, index);
186             }
187         }
188     }
189 
190     void UpdateParameters()
191     {
192         for (int i = 0; i < m_ParticleCount; ++i)
193         {
194             Particle p = m_Particles[i];
195             p.m_Damping = m_Damping;
196             p.m_Elasticity = m_Elasticity;
197             p.m_Stiffness = m_Stiffness;
198             p.m_Inert = m_Inert;
199             p.m_Damping = Mathf.Clamp01(p.m_Damping);
200             p.m_Elasticity = Mathf.Clamp01(p.m_Elasticity);
201             p.m_Stiffness = Mathf.Clamp01(p.m_Stiffness);
202             p.m_Inert = Mathf.Clamp01(p.m_Inert);
203 
204             m_Particles[i] = p;
205         }
206     }
207 
208 
209 }

DynamicBoneFastMgr.cs

  1 using System.Collections.Generic;
  2 using Tools;
  3 using Unity.Burst;
  4 using Unity.Collections;
  5 using Unity.Jobs;
  6 using Unity.Mathematics;
  7 using UnityEngine;
  8 using UnityEngine.Jobs;
  9 
 10 public class DynamicBoneFastMgr : SingletonMonoBehaviour<DynamicBoneFastMgr>
 11 {
 12     #region Job
 13 
 14     [BurstCompile]
 15     struct RootPosApplyJob : IJobParallelForTransform
 16     {
 17         public NativeArray<DynamicBoneFast.HeadInfo> ParticleHeadInfo;
 18 
 19         public void Execute(int index, TransformAccess transform)
 20         {
 21             DynamicBoneFast.HeadInfo headInfo = ParticleHeadInfo[index];
 22             headInfo.m_RootParentBoneWorldPos = transform.position;
 23             headInfo.m_RootParentBoneWorldRot = transform.rotation;
 24 
 25             ParticleHeadInfo[index] = headInfo;
 26         }
 27     }
 28 
 29     [BurstCompile]
 30     struct PrepareParticleJob : IJob
 31     {
 32         [ReadOnly]
 33         public NativeArray<DynamicBoneFast.HeadInfo> ParticleHeadInfo;
 34         public NativeArray<DynamicBoneFast.Particle> ParticleInfo;
 35         public int HeadCount;
 36 
 37         public void Execute()
 38         {
 39             for (int i = 0; i < HeadCount; i++)
 40             {
 41                 DynamicBoneFast.HeadInfo curHeadInfo = ParticleHeadInfo[i];
 42 
 43                 float3 parentPosition = curHeadInfo.m_RootParentBoneWorldPos;
 44                 quaternion parentRotation = curHeadInfo.m_RootParentBoneWorldRot;
 45 
 46                 for (int j = 0; j < curHeadInfo.m_particleCount; j++)
 47                 {
 48                     int pIdx = curHeadInfo.m_jobDataOffset + j;
 49                     DynamicBoneFast.Particle p = ParticleInfo[pIdx];
 50 
 51                     var localPosition = p.localPosition * p.parentScale;
 52                     var localRotation = p.localRotation;
 53                     var worldPosition = parentPosition + math.mul(parentRotation, localPosition);
 54                     var worldRotation = math.mul(parentRotation, localRotation);
 55 
 56                     p.worldPosition = worldPosition;
 57                     p.worldRotation = worldRotation;
 58 
 59                     parentPosition = worldPosition;
 60                     parentRotation = worldRotation;
 61 
 62                     ParticleInfo[pIdx] = p;
 63                 }
 64             }
 65         }
 66     }
 67 
 68     [BurstCompile]
 69     struct UpdateParticles1Job : IJobParallelFor
 70     {
 71         [ReadOnly]
 72         public NativeArray<DynamicBoneFast.HeadInfo> ParticleHeadInfo;
 73         public NativeArray<DynamicBoneFast.Particle> ParticleInfo;
 74         public int HeadCount;
 75 
 76         public void Execute(int index)
 77         {
 78             int headIndex = index / DynamicBoneFast.MAX_TRANSFORM_LIMIT;
 79             DynamicBoneFast.HeadInfo curHeadInfo = ParticleHeadInfo[headIndex];
 80             int singleId = index % DynamicBoneFast.MAX_TRANSFORM_LIMIT;
 81 
 82             if (singleId >= curHeadInfo.m_particleCount)
 83                 return;
 84 
 85             int pIdx = curHeadInfo.m_jobDataOffset + (index % DynamicBoneFast.MAX_TRANSFORM_LIMIT);
 86 
 87             DynamicBoneFast.Particle p = ParticleInfo[pIdx];
 88 
 89             if (p.m_ParentIndex >= 0)
 90             {
 91                 float3 ev = p.tmpWorldPosition - p.tmpPrevWorldPosition;
 92                 float3 evrmove = curHeadInfo.m_ObjectMove * p.m_Inert;
 93                 p.tmpPrevWorldPosition = p.tmpWorldPosition + evrmove;
 94 
 95                 float edamping = p.m_Damping;
 96                 float3 tmp = ev * (1 - edamping) + evrmove;
 97                 p.tmpWorldPosition += tmp;
 98             }
 99             else
100             {
101                 p.tmpPrevWorldPosition = p.tmpWorldPosition;
102                 p.tmpWorldPosition = p.worldPosition;
103             }
104 
105             ParticleInfo[pIdx] = p;
106         }
107     }
108 
109     [BurstCompile]
110     struct UpdateParticle2Job : IJobParallelFor
111     {
112         [ReadOnly]
113         public NativeArray<DynamicBoneFast.HeadInfo> ParticleHeadInfo;
114         public NativeArray<DynamicBoneFast.Particle> ParticleInfo;
115         public int HeadCount;
116         public float DeltaTime;
117 
118         public void Execute(int index)
119         {
120             if (index % DynamicBoneFast.MAX_TRANSFORM_LIMIT == 0)
121                 return;
122 
123             int headIndex = index / DynamicBoneFast.MAX_TRANSFORM_LIMIT;
124             DynamicBoneFast.HeadInfo curHeadInfo = ParticleHeadInfo[headIndex];
125 
126             int singleId = index % DynamicBoneFast.MAX_TRANSFORM_LIMIT;
127 
128             if (singleId >= curHeadInfo.m_particleCount)
129                 return;
130 
131             int pIdx = curHeadInfo.m_jobDataOffset + (index % DynamicBoneFast.MAX_TRANSFORM_LIMIT);
132 
133             DynamicBoneFast.Particle p = ParticleInfo[pIdx];
134             int p0Idx = curHeadInfo.m_jobDataOffset + p.m_ParentIndex;
135             DynamicBoneFast.Particle p0 = ParticleInfo[p0Idx];
136 
137             float3 ePos = p.worldPosition;
138             float3 ep0Pos = p0.worldPosition;
139 
140             float erestLen = math.distance(ep0Pos, ePos);
141 
142             float stiffness = p.m_Stiffness;
143             if (stiffness > 0 || p.m_Elasticity > 0)
144             {
145                 float4x4 em0 = float4x4.TRS(p0.tmpWorldPosition, p0.worldRotation, p.parentScale);
146                 float3 erestPos = math.mul(em0, new float4(p.localPosition.xyz, 1)).xyz;
147                 float3 ed = erestPos - p.tmpWorldPosition;
148                 float3 eStepElasticity = ed * p.m_Elasticity * curHeadInfo.m_UpdateRate * DeltaTime;
149                 p.tmpWorldPosition += eStepElasticity;
150 
151                 if (stiffness > 0)
152                 {
153                     float len = math.distance(erestPos, p.tmpWorldPosition);
154                     float maxlen = erestLen * (1 - stiffness) * 2;
155                     if (len > maxlen)
156                     {
157                         float3 max = ed * ((len - maxlen) / len);
158                         p.tmpWorldPosition += max;
159                     }
160                 }
161             }
162 
163             float3 edd = p0.tmpWorldPosition - p.tmpWorldPosition;
164             float eleng = math.distance(p0.tmpWorldPosition, p.tmpWorldPosition);
165             if (eleng > 0)
166             {
167                 float3 tmp = edd * ((eleng - erestLen) / eleng);
168                 p.tmpWorldPosition += tmp;
169             }
170 
171             ParticleInfo[pIdx] = p;
172         }
173     }
174 
175     [BurstCompile]
176     struct ApplyParticleToTransform : IJobParallelFor
177     {
178         [ReadOnly]
179         public NativeArray<DynamicBoneFast.HeadInfo> ParticleHeadInfo;
180         public NativeArray<DynamicBoneFast.Particle> ParticleInfo;
181         public int HeadCount;
182 
183         public void Execute(int index)
184         {
185             if (index % DynamicBoneFast.MAX_TRANSFORM_LIMIT == 0)
186                 return;
187 
188             int headIndex = index / DynamicBoneFast.MAX_TRANSFORM_LIMIT;
189 
190             DynamicBoneFast.HeadInfo curHeadInfo = ParticleHeadInfo[headIndex];
191             int singleId = index % DynamicBoneFast.MAX_TRANSFORM_LIMIT;
192 
193             if (singleId >= curHeadInfo.m_particleCount)
194                 return;
195 
196             int pIdx = curHeadInfo.m_jobDataOffset + (index % DynamicBoneFast.MAX_TRANSFORM_LIMIT);
197 
198             DynamicBoneFast.Particle p = ParticleInfo[pIdx];
199             int p0Idx = curHeadInfo.m_jobDataOffset + p.m_ParentIndex;
200             DynamicBoneFast.Particle p0 = ParticleInfo[p0Idx];
201 
202             if (p0.m_ChildCount <= 1)
203             {
204                 float3 ev = p.localPosition;
205                 float3 ev2 = p.tmpWorldPosition - p0.tmpWorldPosition;
206 
207                 float4x4 epm = float4x4.TRS(p.worldPosition, p.worldRotation, p.parentScale);
208 
209                 var worldV = math.mul(epm, new float4(ev, 0)).xyz;
210                 Quaternion erot = Quaternion.FromToRotation(worldV, ev2);
211                 var eoutputRot = math.mul(erot, p.worldRotation);
212                 p0.worldRotation = eoutputRot;
213             }
214 
215             p.worldPosition = p.tmpWorldPosition;
216 
217             ParticleInfo[pIdx] = p;
218             ParticleInfo[p0Idx] = p0;
219         }
220     }
221 
222     [BurstCompile]
223     struct FinalJob : IJobParallelForTransform
224     {
225         [ReadOnly]
226         public NativeArray<DynamicBoneFast.Particle> ParticleInfo;
227 
228         public void Execute(int index, TransformAccess transform)
229         {
230             transform.rotation = ParticleInfo[index].worldRotation;
231             transform.position = ParticleInfo[index].worldPosition;
232         }
233     }
234 
235     #endregion
236 
237 
238     List<DynamicBoneFast> m_DynamicBoneList;
239     NativeList<DynamicBoneFast.Particle> m_ParticleInfoList;
240     NativeList<DynamicBoneFast.HeadInfo> m_HeadInfoList;
241 
242     TransformAccessArray m_headRootTransform;
243     TransformAccessArray m_particleTransformArr;
244     int m_DbDataLen;
245     JobHandle m_lastJobHandle;
246 
247     Queue<DynamicBoneFast> m_loadingQueue = new Queue<DynamicBoneFast>();
248     Queue<DynamicBoneFast> m_removeQueue = new Queue<DynamicBoneFast>();
249 
250 
251     void Awake()
252     {
253         m_DynamicBoneList = new List<DynamicBoneFast>();
254         m_ParticleInfoList = new NativeList<DynamicBoneFast.Particle>(Allocator.Persistent);
255         m_HeadInfoList = new NativeList<DynamicBoneFast.HeadInfo>(Allocator.Persistent);
256         m_particleTransformArr = new TransformAccessArray(200 * DynamicBoneFast.MAX_TRANSFORM_LIMIT, 64);
257         m_headRootTransform = new TransformAccessArray(200, 64);
258     }
259 
260     void UpdateQueue()
261     {
262         while (m_loadingQueue.Count > 0)
263         {
264             DynamicBoneFast target = m_loadingQueue.Dequeue();
265             int index = m_DynamicBoneList.IndexOf(target);
266             if (index != -1)
267                 continue;
268 
269             m_DynamicBoneList.Add(target);
270             target.m_headInfo.m_jobDataOffset = m_ParticleInfoList.Length;
271             target.m_headInfo.HeadIndex = m_HeadInfoList.Length;
272             m_HeadInfoList.Add(target.m_headInfo);
273             m_ParticleInfoList.AddRange(target.m_Particles);
274             m_headRootTransform.Add(target.m_rootParentTransform);
275 
276             for (int i = 0; i < DynamicBoneFast.MAX_TRANSFORM_LIMIT; i++)
277                 m_particleTransformArr.Add(target.m_particleTransformArr[i]);
278 
279             m_DbDataLen++;
280         }
281 
282         while (m_removeQueue.Count > 0)
283         {
284             DynamicBoneFast target = m_removeQueue.Dequeue();
285             int index = m_DynamicBoneList.IndexOf(target);
286             if (index != -1)
287             {
288                 m_DynamicBoneList.RemoveAt(index);
289 
290                 int curHeadIndex = target.m_headInfo.HeadIndex;
291 
292                 //是否是队列中末尾对象
293                 bool isEndTarget = curHeadIndex == m_HeadInfoList.Length - 1;
294                 if (isEndTarget)
295                 {
296                     m_HeadInfoList.RemoveAtSwapBack(curHeadIndex);
297                     m_headRootTransform.RemoveAtSwapBack(curHeadIndex);
298 
299                     for (int i = DynamicBoneFast.MAX_TRANSFORM_LIMIT - 1; i >= 0; i--)
300                     {
301                         int dataOffset = curHeadIndex * DynamicBoneFast.MAX_TRANSFORM_LIMIT + i;
302                         m_ParticleInfoList.RemoveAtSwapBack(dataOffset);
303                         m_particleTransformArr.RemoveAtSwapBack(dataOffset);
304                     }
305                 }
306                 else
307                 {
308                     //将最末列的HeadInfo 索引设置为当前将要移除的HeadInfo 索引
309                     DynamicBoneFast lastTarget = m_DynamicBoneList[m_DynamicBoneList.Count - 1];
310                     m_DynamicBoneList.RemoveAt(m_DynamicBoneList.Count - 1);
311                     m_DynamicBoneList.Insert(index, lastTarget);
312 
313                     DynamicBoneFast.HeadInfo lastHeadInfo = lastTarget.ResetHeadIndexAndDataOffset(curHeadIndex);
314                     m_HeadInfoList.RemoveAtSwapBack(curHeadIndex);
315                     m_HeadInfoList[curHeadIndex] = lastHeadInfo;
316                     m_headRootTransform.RemoveAtSwapBack(curHeadIndex);
317 
318                     for (int i = DynamicBoneFast.MAX_TRANSFORM_LIMIT - 1; i >= 0; i--)
319                     {
320                         int dataOffset = curHeadIndex * DynamicBoneFast.MAX_TRANSFORM_LIMIT + i;
321                         m_ParticleInfoList.RemoveAtSwapBack(dataOffset);
322                         m_particleTransformArr.RemoveAtSwapBack(dataOffset);
323                     }
324                 }
325 
326                 m_DbDataLen--;
327             }
328 
329             target.ClearJobData();
330         }
331     }
332 
333     public void OnEnter(DynamicBoneFast target)
334     {
335         m_loadingQueue.Enqueue(target);
336     }
337 
338     public void OnExit(DynamicBoneFast target)
339     {
340         m_removeQueue.Enqueue(target);
341     }
342 
343     void LateUpdate()
344     {
345         if (!m_lastJobHandle.IsCompleted)
346             return;
347 
348         m_lastJobHandle.Complete();
349 
350         UpdateQueue();
351 
352         if (m_DbDataLen == 0)
353             return;
354 
355         var dataArrLength = m_DbDataLen * DynamicBoneFast.MAX_TRANSFORM_LIMIT;
356 
357         var rootJob = new RootPosApplyJob
358         {
359             ParticleHeadInfo = m_HeadInfoList
360         };
361         var rootHandle = rootJob.Schedule(m_headRootTransform);
362 
363         var prepareJob = new PrepareParticleJob
364         {
365             ParticleHeadInfo = m_HeadInfoList,
366             ParticleInfo = m_ParticleInfoList,
367             HeadCount = m_DbDataLen
368         };
369         var prepareHandle = prepareJob.Schedule(rootHandle);
370 
371         var update1Job = new UpdateParticles1Job
372         {
373             ParticleHeadInfo = m_HeadInfoList,
374             ParticleInfo = m_ParticleInfoList,
375             HeadCount = m_DbDataLen
376         };
377         var update1Handle = update1Job.Schedule(dataArrLength, DynamicBoneFast.MAX_TRANSFORM_LIMIT, prepareHandle);
378 
379         var update2Job = new UpdateParticle2Job
380         {
381             ParticleHeadInfo = m_HeadInfoList,
382             ParticleInfo = m_ParticleInfoList,
383             HeadCount = m_DbDataLen,
384             DeltaTime = Time.deltaTime,
385         };
386         var update2Handle = update2Job.Schedule(dataArrLength, DynamicBoneFast.MAX_TRANSFORM_LIMIT, update1Handle);
387 
388         var appTransJob = new ApplyParticleToTransform
389         {
390             ParticleHeadInfo = m_HeadInfoList,
391             ParticleInfo = m_ParticleInfoList,
392             HeadCount = m_DbDataLen
393         };
394 
395         var appTransHandle = appTransJob.Schedule(dataArrLength, DynamicBoneFast.MAX_TRANSFORM_LIMIT, update2Handle);
396         var finalJob = new FinalJob
397         {
398             ParticleInfo = m_ParticleInfoList,
399         };
400         var finalHandle = finalJob.Schedule(m_particleTransformArr, appTransHandle);
401 
402         m_lastJobHandle = finalHandle;
403 
404         JobHandle.ScheduleBatchedJobs();
405     }
406 
407     void OnDestroy()
408     {
409         if (m_particleTransformArr.isCreated)
410         {
411             m_particleTransformArr.Dispose();
412         }
413 
414         if (m_ParticleInfoList.IsCreated)
415         {
416             m_ParticleInfoList.Dispose();
417         }
418 
419         if (m_HeadInfoList.IsCreated)
420         {
421             m_HeadInfoList.Dispose();
422         }
423 
424         if (m_headRootTransform.isCreated)
425         {
426             m_headRootTransform.Dispose();
427         }
428     }
429 }

转载请注明出处:https://www.cnblogs.com/jietian331/p/17154522.html

posted @ 2023-02-25 15:29  孤独の巡礼  阅读(310)  评论(0编辑  收藏  举报