unity蒙皮网格的换装实现

本文转载自:http://www.cnblogs.com/shamoyuu/p/6505561.html

 

一、换装原理

  游戏角色换装分为以下几步:

    1.替换蒙皮网格

    2.刷新骨骼

    3.替换材质

 

  上面这种是比较简单的换装,可以实现,但是一般我们为了降低游戏的Draw Call会合并模型的网格,这就需要我们重新计算UV,还要合并贴图和材质。这种复杂的实现分为以下几步:

    1.替换蒙皮网格(或者直接替换模型换装部位的GameObject,因为合并的时候会合并所有的蒙皮网格,而不会关心它是否属于原来角色身体的一部分,而且如果需要替换的部位有多个配件拥有独立的网格和贴图,那这种方式都可以正常执行。我下面的代码就是直接替换了换装部位的GameObject)

    2.合并所有蒙皮网格

    3.刷新骨骼

    4.附加材质(我下面是获取第一个材质作为默认材质)

    5.合并贴图(贴图的宽高最好是2的N次方的值)

    6.重新计算UV

 

二、换装实现

  1 using UnityEngine;
  2 using System.Collections.Generic;
  3 
  4 
  5 public class CharacterCombine : MonoBehaviour
  6 {
  7     // 目标物体(角色根物体)
  8     public GameObject target;
  9 
 10     // 需要替换的部位(这里以body做示例)
 11     public GameObject targetBody;
 12 
 13     // 用来替换上面部位的物体(这是一个独立的fbx文件,它里面可以包含多个子物体和贴图,但是必须有骨骼及蒙皮)
 14     public GameObject bodyTest;
 15 
 16 
 17     void Start()
 18     {
 19         //CombineTest();
 20     }
 21 
 22 
 23     public void CombineTest()
 24     {
 25         Debug.Log("模型合并开始");
 26 
 27         // 为角色增加换装物体
 28         GameObject go = Instantiate(bodyTest);
 29         go.transform.parent = target.transform;
 30 
 31         // 禁用角色原来的body,这样就不会计算它的网格
 32         targetBody.SetActive(false);
 33 
 34         // 合并蒙皮网格和贴图,刷新骨骼
 35         Combine(target.transform);
 36 
 37         Destroy(go);
 38     }
 39 
 40 
 41     /// <summary>
 42     /// 合并蒙皮网格,刷新骨骼
 43     /// 注意:合并后的网格会使用同一个Material
 44     /// </summary>
 45     /// <param name="root">角色根物体</param>
 46     public void Combine(Transform root)
 47     {
 48         float startTime = Time.realtimeSinceStartup;
 49 
 50         List<CombineInstance> combineInstances = new List<CombineInstance>();
 51         Material material = null;
 52         List<Transform> boneList = new List<Transform>();
 53         Transform[] transforms = root.GetComponentsInChildren<Transform>();
 54         List<Texture2D> textures = new List<Texture2D>();
 55 
 56         int width = 0;
 57         int height = 0;
 58 
 59         int uvCount = 0;
 60 
 61         List<Vector2[]> uvList = new List<Vector2[]>();
 62 
 63         // 遍历所有蒙皮网格渲染器,以计算出所有需要合并的网格、UV、骨骼的信息
 64         foreach (SkinnedMeshRenderer smr in root.GetComponentsInChildren<SkinnedMeshRenderer>())
 65         {
 66             if (material == null)
 67             {
 68                 material = Instantiate(smr.sharedMaterial);
 69             }
 70 
 71             for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
 72             {
 73                 CombineInstance ci = new CombineInstance();
 74                 ci.mesh = smr.sharedMesh;
 75                 ci.subMeshIndex = sub;
 76                 combineInstances.Add(ci);
 77             }
 78 
 79             uvList.Add(smr.sharedMesh.uv);
 80             uvCount += smr.sharedMesh.uv.Length;
 81 
 82             if (smr.material.mainTexture != null)
 83             {
 84                 textures.Add(smr.GetComponent<Renderer>().material.mainTexture as Texture2D);
 85                 width += smr.GetComponent<Renderer>().material.mainTexture.width;
 86                 height += smr.GetComponent<Renderer>().material.mainTexture.height;
 87             }
 88 
 89             foreach (Transform bone in smr.bones)
 90             {
 91                 foreach (Transform item in transforms)
 92                 {
 93                     if (item.name != bone.name) continue;
 94                     boneList.Add(item);
 95                     break;
 96                 }
 97             }
 98 
 99             smr.gameObject.SetActive(false);
100         }
101 
102         // 获取并配置角色所有的SkinnedMeshRenderer
103         SkinnedMeshRenderer tempRenderer = root.gameObject.GetComponent<SkinnedMeshRenderer>();
104         if (!tempRenderer)
105         {
106             tempRenderer = root.gameObject.AddComponent<SkinnedMeshRenderer>();
107         }
108 
109         tempRenderer.sharedMesh = new Mesh();
110 
111         // 合并网格,刷新骨骼,附加材质
112         tempRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
113         tempRenderer.bones = boneList.ToArray();
114         tempRenderer.material = material;
115 
116         Texture2D skinnedMeshAtlas = new Texture2D(get2Pow(width), get2Pow(height));
117         Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), 0);
118         Vector2[] atlasUVs = new Vector2[uvCount];
119 
120         // 因为将贴图都整合到了一张图片上,所以需要重新计算UV
121         int j = 0;
122         for (int i = 0; i < uvList.Count; i++)
123         {
124             foreach (Vector2 uv in uvList[i])
125             {
126                 atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
127                 atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
128                 j++;
129             }
130         }
131 
132         // 设置贴图和UV
133         tempRenderer.material.mainTexture = skinnedMeshAtlas;
134         tempRenderer.sharedMesh.uv = atlasUVs;
135 
136         Debug.Log("合并耗时 : " + (Time.realtimeSinceStartup - startTime) * 1000 + " ms");
137     }
138 
139 
140     /// <summary>
141     /// 获取最接近输入值的2的N次方的数,最大不会超过1024,例如输入320会得到512
142     /// </summary>
143     public int get2Pow(int into)
144     {
145         int outo = 1;
146         for (int i = 0; i < 10; i++)
147         {
148             outo *= 2;
149             if (outo > into)
150             {
151                 break;
152             }
153         }
154 
155         return outo;
156     }
157 }

 

三、效果展示

在执行上面的代码前,角色的每个部分都是单独的,并且是激活的状态。

执行后,角色所有的部位都隐藏了起来,因为它们的网格都合并到了Player_Girl这个角色根物体上。

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值