zoukankan      html  css  js  c++  java
  • 【Unity3D】Unity3D SkinnedMeshRenderer换装系统

    转载请注明出处: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

    二、换装实现

    using System;
    using System.Collections;
    using UnityEngine;
    using System.Collections.Generic;
    
    
    public class CharacterCombine : MonoBehaviour
    {
        // 目标物体(必须是骨骼的父物体,不然蒙皮失效)
        public GameObject target;
    
        // 最终材质(合并所有模型后使用的材质)
        public Material material;
    
    
    
        // 物体所有的部分
        private GameObject[] targetParts = new GameObject[9];
    
        private string[] defaultEquipPartPaths = new string[9];
    
        void Start()
        {
            // 把FBX的模型按部件分别放入Resources下对应的文件夹里,可以留空,模型需要蒙皮,而且所有模型使用同一骨骼
            // 最后的M是Fbx的模型,需要的Unity3D里设置好材质和贴图,部件贴图要勾选Read/Write Enabled
            defaultEquipPartPaths[0] = "Model/Player/GirlPlayer/Head/Head0000/M";
            defaultEquipPartPaths[1] = "Model/Player/GirlPlayer/Face/Face0000/M";
            defaultEquipPartPaths[2] = "Model/Player/GirlPlayer/Hair/Hair0000/M";
            defaultEquipPartPaths[3] = "";
            defaultEquipPartPaths[4] = "Model/Player/GirlPlayer/Body/Body0000/M";
            defaultEquipPartPaths[5] = "Model/Player/GirlPlayer/Leg/Leg0000/M";
            defaultEquipPartPaths[6] = "Model/Player/GirlPlayer/Hand/Hand0000/M";
            defaultEquipPartPaths[7] = "Model/Player/GirlPlayer/Foot/Foot0000/M";
            defaultEquipPartPaths[8] = "Model/Player/GirlPlayer/Wing/Wing0001/M";
    
            Destroy(target.GetComponent<SkinnedMeshRenderer>());
            for (int i = 0; i < defaultEquipPartPaths.Length; i++)
            {
                UnityEngine.Object o = Resources.Load(defaultEquipPartPaths[i]);
                if (o)
                {
                    GameObject go = Instantiate(o) as GameObject;
                    go.transform.parent = target.transform;
                    go.transform.localPosition = new Vector3(0, -1000, 0);
                    go.transform.localRotation = new Quaternion();
                    targetParts[i] = go;
                }
            }
    
            StartCoroutine(DoCombine());
        }
    
        /// <summary>
        /// 使用延时,不然某些GameObject还没有创建
        /// </summary>
        /// <returns></returns>
        IEnumerator DoCombine()
        {
            yield return null;
            Combine(target.transform);
        }
    
    
        /// <summary>
        /// 合并蒙皮网格,刷新骨骼
        /// 注意:合并后的网格会使用同一个Material
        /// </summary>
        /// <param name="root">角色根物体</param>
        private void Combine(Transform root)
        {
            float startTime = Time.realtimeSinceStartup;
    
            List<CombineInstance> combineInstances = new List<CombineInstance>();
            List<Transform> boneList = new List<Transform>();
            Transform[] transforms = root.GetComponentsInChildren<Transform>();
            List<Texture2D> textures = new List<Texture2D>();
    
            int width = 0;
            int height = 0;
    
            int uvCount = 0;
    
            List<Vector2[]> uvList = new List<Vector2[]>();
    
            // 遍历所有蒙皮网格渲染器,以计算出所有需要合并的网格、UV、骨骼的信息
            foreach (SkinnedMeshRenderer smr in root.GetComponentsInChildren<SkinnedMeshRenderer>())
            {
                for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
                {
                    CombineInstance ci = new CombineInstance();
                    ci.mesh = smr.sharedMesh;
                    ci.subMeshIndex = sub;
                    combineInstances.Add(ci);
                }
    
                uvList.Add(smr.sharedMesh.uv);
                uvCount += smr.sharedMesh.uv.Length;
    
                if (smr.material.mainTexture != null)
                {
                    textures.Add(smr.GetComponent<Renderer>().material.mainTexture as Texture2D);
                    width += smr.GetComponent<Renderer>().material.mainTexture.width;
                    height += smr.GetComponent<Renderer>().material.mainTexture.height;
                }
    
                foreach (Transform bone in smr.bones)
                {
                    foreach (Transform item in transforms)
                    {
                        if (item.name != bone.name) continue;
                        boneList.Add(item);
                        break;
                    }
                }
            }
    
            // 获取并配置角色所有的SkinnedMeshRenderer
            SkinnedMeshRenderer tempRenderer = root.gameObject.GetComponent<SkinnedMeshRenderer>();
            if (!tempRenderer)
            {
                tempRenderer = root.gameObject.AddComponent<SkinnedMeshRenderer>();
            }
    
            tempRenderer.sharedMesh = new Mesh();
    
            // 合并网格,刷新骨骼,附加材质
            tempRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
            tempRenderer.bones = boneList.ToArray();
            tempRenderer.material = material;
    
            Texture2D skinnedMeshAtlas = new Texture2D(get2Pow(width), get2Pow(height));
            Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), 0);
            Vector2[] atlasUVs = new Vector2[uvCount];
    
            // 因为将贴图都整合到了一张图片上,所以需要重新计算UV
            int j = 0;
            for (int i = 0; i < uvList.Count; i++)
            {
                foreach (Vector2 uv in uvList[i])
                {
                    atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
                    atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
                    j++;
                }
            }
    
            // 设置贴图和UV
            tempRenderer.material.mainTexture = skinnedMeshAtlas;
            tempRenderer.sharedMesh.uv = atlasUVs;
    
            // 销毁所有部件
            foreach (GameObject goTemp in targetParts)
            {
                if (goTemp)
                {
                    Destroy(goTemp);
                }
            }
    
            Debug.Log("合并耗时 : " + (Time.realtimeSinceStartup - startTime) * 1000 + " ms");
        }
    
    
        /// <summary>
        /// 获取最接近输入值的2的N次方的数,最大不会超过1024,例如输入320会得到512
        /// </summary>
        private int get2Pow(int into)
        {
            int outo = 1;
            for (int i = 0; i < 10; i++)
            {
                outo *= 2;
                if (outo > into)
                {
                    break;
                }
            }
    
            return outo;
        }
    }

    三、效果展示

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

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

     

    模型就不放出来了~

  • 相关阅读:
    Bzoj 2134: [国家集训队2011]单选错位(期望)
    洛谷 P1230 智力大冲浪
    洛谷 P2695 骑士的工作
    洛谷 P1551 亲戚
    洛谷 P1111 修复公路
    洛谷 P1599 结算日
    HDU 1166 敌兵布阵
    洛谷 P2212 [USACO14MAR]浇地Watering the Fields
    洛谷 P1506 拯救oibh总部
    洛谷 P1396 营救
  • 原文地址:https://www.cnblogs.com/shamoyuu/p/6505561.html
Copyright © 2011-2022 走看看