【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這個角色根物體上。

 

 

模型就不放出來了~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM