

捏脸和换装功能
UnityJun24
阅读 1977
2023年12月4日
现在的数字人以及游戏中的换装功能是如何实现的Avatar
Unity 使用Avatar系统来识别布局中的特定动画模型是否为人形,以及模型的哪些部分对应于腿、手臂、头和躯干。
由于不同人形角色之间骨骼结构的相似性,可将动画从一个人形角色映射到另一个角色,允许重定位和反向运动学 (IK)。
我们分别来说一下捏脸和换装功能
1.捏脸一般是两种,一种是融合变形,另外一种就是骨骼捏脸。融合变形就是BlendShape,需要美术制作融合的脸型注意和表情的BS分开然后就是制作UI Slider 代码绑定Slider来驱动带有BS的SkinnedMeshRender的BS,来捏脸,这种比较受限制而且依赖美术偏多,但是效果比较好;另外骨骼捏脸就需要美术绑定骨骼并且注意结点层次结构具体和命名的结构和命名后边单独文章说明。让后就是获取需要的骨骼点把坐标转换到屏幕坐标,生成点,实现拖动点的(x,y)坐标驱动骨骼点坐标来进行捏脸功能
2.换装功能主要是骨骼的重定向,其他的就是替换对应的部位(一种是单独替换mesh和material这种就要求替换的部位和被替换的部位的mesh和子物体的mesh以及material(数量)统一,另外就是直接替换整个部位的gameobject都可以,虽然替换整个比较方便,但是对部分就不叫不方便,比如如果上衣包含了胳膊部位(为了防止穿模胳膊和上衣做成一体),你改变了肤色,如果替换整个Gameobject那么你还需要重新设置胳膊颜色,单独设置mesh和material就可以过滤掉受影响的部分。重点就是骨骼重定向,因为相同类型的部位的不同位置或者没事制作的坐标不一致,当然最重要的是换装后,动画能够正常,这就是骨骼确定的,动画最终驱动的是骨骼,重定向就是需要换装的部位的骨骼去被替换的部件找到他绑定的骨骼,绑定设置给需要替换的部件上。
最后需要说明的是部件拆分,部件类型定义清晰是这种项目的关键,部件打包成AB包
代码片段: 骨骼重定向
public void ShareSkeletonInstanceWith(GameObject resourceMeshRenderer,SkinnedMeshRenderer selfSkin, GameObject target) { Transform[] newBones = new Transform[selfSkin.bones.Length]; for (int i = 0; i < selfSkin.bones.GetLength(0); ++i) { GameObject bone = selfSkin.bones[i].gameObject; //删除原来骨骼 Destroy(selfSkin.bones[i].gameObject); //删除a 1 // 目标的SkinnedMeshRenderer.bones保存的只是目标mesh相关的骨骼,要获得目标全部骨骼,可以通过查找的方式. newBones[i] = FindChildRecursion(target.transform, bone.name); } // UnityControllerImpl.Log_M("selfSkinname:"+ selfSkin); selfSkin.bones = newBones; // //删除已经更新后留下来的部件骨骼 //删除a 2 Transform[] taChild = resourceMeshRenderer.GetComponentsInChildren<Transform>(); for (int i=1;i<taChild.Length;i++ ) { if (taChild[i].TryGetComponent(typeof(SkinnedMeshRenderer), out Component component)) return; Destroy(taChild[i].gameObject); } } // 递归查找 public Transform FindChildRecursion(Transform t, string name) { foreach (Transform child in t) { if (child .name == name) { // UnityControllerImpl.Log_M("骨骼的名字:"+child); return child; } else { Transform ret = FindChildRecursion(child, name); if (ret != null) return ret; } } return null; }
换装逻辑
protected SkinnedMeshRenderer GetPartMeshRenderer() { var partName = GetPartName(); SkinnedMeshRenderer tempSkinnedMeshRenderer = null; var goLoadMode = gameObject.GetComponent<Loadmode>(); if (!goLoadMode.DicSkinnedMeshRenderers.ContainsKey(partName) && !goLoadMode.DicSkinnedMeshRenderersCut.ContainsKey(partName)) { UnityControllerImpl.Log_M("UnityTag: 请先设置身体"); } else { tempSkinnedMeshRenderer = goLoadMode.im ? goLoadMode.DicSkinnedMeshRenderers[partName] : goLoadMode.DicSkinnedMeshRenderersCut[partName]; } if (tempSkinnedMeshRenderer == null) tempSkinnedMeshRenderer = new SkinnedMeshRenderer(); return tempSkinnedMeshRenderer; } public bool SetPartMaterialColor(string color) { var tempSkinnedMeshRenderers = GetPartMeshRenderer().GetComponentsInChildren<SkinnedMeshRenderer>(); if (tempSkinnedMeshRenderers.Length < 2) return false; var tempSkinnedMeshRenderer = tempSkinnedMeshRenderers[1]; if (tempSkinnedMeshRenderer.material.shader.name == "Unlit/Texture") { UnityControllerImpl.Log_M("UnityTag : SetBeardColor 的材质的shader类型不可设置颜色"); return false; } tempSkinnedMeshRenderer.sharedMaterial.color = ConvertColor.HexToColor(color); return true; } public void SetPartMaterialColorNoReturn(string color) { SetPartMaterialColor(color); } protected void UpdateMesh(SkinnedMeshRenderer partMeshRenderer, GameObject resourceMeshRenderer) { ShareSkeletonInstanceWith(resourceMeshRenderer,resourceMeshRenderer.GetComponentInChildren<SkinnedMeshRenderer>(),GetComponent<Loadmode>().bones); if( partMeshRenderer.transform.childCount > 0) { if (partMeshRenderer.GetComponentsInChildren<Transform>()[1].gameObject.transform.localScale.x != 0) { UnityControllerImpl.Log_M("UnityTag:生成之前设置失败,删除在生成之后才完成"); Destroy(partMeshRenderer.GetComponentsInChildren<Transform>()[1].gameObject); } } resourceMeshRenderer.transform.parent = partMeshRenderer.transform; // resourceMeshRenderer.layer = LayerMask.NameToLayer(LayerMask.LayerToName(partMeshRenderer.gameObject.layer)); Transform[] transforms = resourceMeshRenderer.GetComponentsInChildren<Transform>(); // foreach (var trans in transforms) for(int i=0;i<transforms.Length;i++) { transforms[i].gameObject.layer = LayerMask.NameToLayer(LayerMask.LayerToName(partMeshRenderer.gameObject.layer)); } } protected IEnumerator LoadPartFromAb(AssetBundle ab = null, bool fromAbChild = false, string partName = null) { if (ab == null) { UnityControllerImpl.Log_M("Unity_Tag ab is null"); yield return null; } else { var parts = ab.LoadAllAssets<GameObject>(); AssetBundle.UnloadAllAssetBundles(false); var partMeshRenderer = GetPartMeshRenderer(); // UnityControllerImpl.Log_M($"删除之前 部件子物体个数:{partMeshRenderer.transform.childCount}"); if (partMeshRenderer.transform.childCount >0) { UnityControllerImpl.Log_M("更换部件之前上一个部件处理"); // partMeshRenderer.GetComponentsInChildren<Transform>()[1].gameObject.SetActive(false); Destroy(partMeshRenderer.GetComponentsInChildren<Transform>()[1].gameObject); yield return null; } for (int i = parts.Length - 1; i >= 0; i--) { var partInstantiate = Instantiate(parts[i]); if (gameObject.GetComponent<Loadmode>().im) { if (!PartGameObject.ContainsKey(partInstantiate.name)) PartGameObject.Add(partInstantiate.name, partInstantiate); else { PartGameObject[partInstantiate.name] = partInstantiate; } } else { if (!PartGameObject1.ContainsKey(partInstantiate.name)) PartGameObject1.Add(partInstantiate.name, partInstantiate); else { PartGameObject1[partInstantiate.name] = partInstantiate; } } // /隐藏掉之前的对象 避免重影 UpdateMesh(partMeshRenderer, partInstantiate); // Destroy(partInstantiate); } yield return null; } } protected void UpdateTexture(SkinnedMeshRenderer partMeshRenderer, Texture resourceMeshRenderer,int index) { var materials=partMeshRenderer.GetComponentsInChildren<SkinnedMeshRenderer>()[1].materials; UnityControllerImpl.Log_M($"SkinendMesh的名字:{ partMeshRenderer}"); UnityControllerImpl.Log_M($"材质的长度:{ materials.Length};材质的名字:{ materials[0].name}"); UnityControllerImpl.Log_M($"贴图材质的名字:{ materials[0]}"); materials[index].mainTexture = resourceMeshRenderer; } protected abstract string GetPartName(); protected abstract IEnumerator LoadPart(AssetBundle ab = null,string partName=null);
发布于技术交流
0条评论

问
AI
全新AI功能上线
1. 基于Unity微调:专为Unity优化,提供精准高效的支持。
2. 深度集成:内置于团结引擎,随时查阅与学习。
3. 多功能支持:全面解决技术问题与学习需求。

问
AI