【原】Unity 骨骼節點對象優化,AnimatorUtility.OptimizeTransformHierarchy
關鍵接口:AnimatorUtility.OptimizeTransformHierarchy
需求:角色模型換裝,角色模型由多個部位組合而成,暴露的骨骼節點非常多,可以通過AnimatorUtility.OptimizeTransformHierarchy接口進行優化。
但是 Unity 提供的接口AnimatorUtility.OptimizeTransformHierarchy裏面有一些坑,我在這裏進行講解。
接口定義:public static void OptimizeTransformHierarchy(GameObject go, string[] exposedTransforms);
對於該接口Unity的註釋是:
This function will remove all transform hierarchy under GameObject, the animator
will write directly transform matrices into the skin mesh matrices saving alot of CPU cycles.
意思就是使用該接口將會刪除目標對象下的所有transform組件,然後animator將寫入transform移動矩陣到skin mesh矩陣中以節約大量cpu時間。
簡單解釋也就是:將需要更新的transform對象保留,不需要的去掉,省掉很多不必要的計算。
1:骨骼全展開
2:骨骼用OptimizeTransformHierarchy優化後,顯示指定骨骼對象
該接口使用,有幾個條件,在此會列出:
//condition: 1: Animator togather with Render 2: Optimize is selected 3:all bones trans right 4: bone name can‘t contain space char
1: Animator 組件和SkinnedMeshRenderer必須在同一個對象中。
2:OptimizeTransformHierarchy 接口的參數 exposedTransforms是 需要暴露的骨骼名稱數組,並且暴露骨骼名稱不能帶空格,否則模型會出現顯示問題(unity自己接口這樣,我也沒辦法)
3:模型fbx設置Optimize選項必須勾選
4:對SkinnedMeshRenderer中參數bones進行賦值時,必須保證對應transform存在,因為優化接口會刪掉所有對象,然後從新生成骨骼節點對象。如果骨骼對象下有掛點,需要對掛點進行保存,優化完成後再對掛點或者其他額外模型進行還原。
以下是粘貼的核心代碼,用於組合一個角色的多個部位,重新合成mesh,骨骼,材質,暴露指定骨骼節點對象(只暴露掛點)
private void CombineMesh(bool comine) {
//所有部位的SkinnedMeshRenderer對象,用於合成一個SkinnedMeshRenderer
var smrArr = GetAllBodySMR(); List<Transform> bonesdArr = new List<Transform>(); List<Material> matArr = new List<Material>(); combineInstanceArr.Clear(); for (int i = 0; i < smrArr.Count; i++) { var smrItem = smrArr[i]; if (smrItem == null) continue; if(smrItem.sharedMesh == null) { Debug.LogError("合並mesh失敗 sharedMesh=null i=" + i); continue; }
matArr.AddRange(smrItem.sharedMaterials); for (int j = 0; j < smrItem.sharedMesh.subMeshCount; j++) { var ci = new CombineInstance(); ci.mesh = smrItem.sharedMesh; ci.subMeshIndex = j; combineInstanceArr.Add(ci); } for (int k = 0; k < smrItem.bones.Length; k++) { var bone = smrItem.bones[k]; if(m_skeletonTransDic.ContainsKey(bone.name)) { if (m_skeletonTransDic[bone.name] == null) { m_skeletonTransDic.Remove(bone.name); } else { bonesdArr.Add(m_skeletonTransDic[bone.name]); } } } } if (m_wholeBodyRanderer != null) { GameObject.DestroyImmediate(m_wholeBodyRanderer); } //todo:get SkinnedMeshRenderer togather with Animator,because OptimizeTransformHierarchy need to do that. Transform animatorTrans = m_objHostModel.GetComponentInChildren<Animator>().transform; m_wholeBodyRanderer = animatorTrans.gameObject.AddComponent<SkinnedMeshRenderer>(); m_wholeBodyRanderer.sharedMesh = new Mesh(); m_wholeBodyRanderer.sharedMesh.CombineMeshes(combineInstanceArr.ToArray(), comine, false); m_wholeBodyRanderer.bones = bonesdArr.ToArray(); m_wholeBodyRanderer.sharedMaterials = matArr.ToArray(); //Bone Optimize: use interface, AnimatorUtility.OptimizeTransformHierarchy //condition: 1: Animator togather with Render 2: Optimize is selected 3:all bones trans right 4: bone name can‘t contain space char Transform[] allBoneTrans = m_wholeBodyRanderer.gameObject.GetComponentsInChildren<Transform>(false); List<string> exposedTransformsName = new List<string>(); Dictionary<string, Transform> rootGuaBoneDict = new Dictionary<string, Transform>(); bool isAllGuaBoneRight = true;// if has only one bone for (int i = 0; i < allBoneTrans.Length; i++) { //屬於掛點 if (allBoneTrans[i].name.Contains("gua_")) { Transform rootBone = allBoneTrans[i].parent; if (!rootBone.name.Contains(" ")) { if (!exposedTransformsName.Contains(rootBone.name)) { rootGuaBoneDict.Add(rootBone.name, allBoneTrans[i]); exposedTransformsName.Add(rootBone.name); } } else { Debug.LogError("錯誤!掛點骨骼名稱不能包含空格!不將對該模型進行骨骼優化。 骨骼名稱:" + rootBone.name); isAllGuaBoneRight = false; } } } if (isAllGuaBoneRight) { foreach(var item in rootGuaBoneDict) { item.Value.SetParent(null); } //優化暴露的骨骼節點對象,只顯示掛點相關的
AnimatorUtility.OptimizeTransformHierarchy(m_wholeBodyRanderer.gameObject, exposedTransformsName.ToArray()); //transforms has been clear, reset all hook transforms m_skeletonTransDic.Clear(); allBoneTrans = animatorTrans.gameObject.GetComponentsInChildren<Transform>(false); for (int i = 0; i < allBoneTrans.Length; i++) { m_skeletonTransDic.Add(allBoneTrans[i].name, allBoneTrans[i]); } //恢復掛點 foreach (var item in rootGuaBoneDict) { Transform guaBone = m_wholeBodyRanderer.transform.parent.Find(item.Key); if (guaBone != null) { //Debug.Log("quosin:test 恢復掛點:" + guaBone.name); item.Value.SetParent(guaBone); } else { Debug.LogError("錯誤!掛點無法恢復!Bone name:" + item.Key); } } } }
【原】Unity 骨骼節點對象優化,AnimatorUtility.OptimizeTransformHierarchy