Unity性能優化之Combine(聯合)


 

 

官方文檔在這里Optimizing Graphics Performance,里面提到的中心要點就是聯結(combine),聯結,再聯結。而對於具體怎么聯結他卻略過不提,於是只好自己研究。

先解釋下聯結的原理和意思:文檔里說,顯卡對於一個含100個面片的物體的和含1500個面片的物體的渲染消耗幾乎是等價的。所以如果你有N個同一材質的東西,那么把他們聯成同一個物體再統一用一個material那么對於顯卡的渲染消耗就要降低N倍。

再講具體做法,方法有2:
1、是直接在max等工具里聯結好,貼上同一材質再導進來,這方法固然好卻不靈活,而且通常不實用,因為項目里大量同一材質的東西都是unity系統的樹啊花花草草啊石頭等。
2、就是在unity里再聯結,這個要怎么做呢,其實也挺簡單的,經常看Island Demo項目的人應該很早就注意到里面的石頭這些都是連在一起的,原因就在這里,他提供了現成就腳本實現聯結。
    先到Island Demo的Assets/Script下找到CombineChildren.cs和MeshCombineUtility.cs兩個腳本復制到自己的項目文件(我們要用的只是前者,但他會調用后者,沒有后者unity會報錯,所以把后者扔到項目里不管就好)
然后把你項目里那些用同一Materials的東西扔到一個空物體里面去,再把CombineChildren.cs貼到那個空物體上,搞定!
下面,我們來看看這個神奇的腳本幫我們做了什么工作,運行游戲之前,見圖1,Island Demo就是按上述方法做了個_RocksCombined的東西,上面只貼着這個腳本,他里面全是島上個一塊塊小石頭,用的是同一個Materials,見圖2.
運行游戲后,神奇的事情發生了,見圖3,
這個_RocksCombined上面出現了一個Combined Mesh,並帶有他里面石頭的Material,而看圖4.他里面的小石頭的MeshRenderer的鈎都自動去掉了,即被disable了。
這就是這個腳本自動幫我們做的優化方法,實現了對同材質物體的聯結,從而降低了系統開銷。
現在說下這個腳本的缺陷和使用注意,就我目前的使用來看,他只支持單一material,所以如果你在MeshRenderer里用多個materials或里面的子物體含有不同的material就不行了,它會在父物體里生成多個叫Combined Mesh的東西,卻無法把他們賦給父物體。所以,如果你發現你運行游戲后父物體還是沒帶MeshRenderer,請仔細檢查你的子物體是否帶了不同的material!同時也希望論壇里的牛人可以改進下這個腳本來實現對復合材質的聯結支持,我有空以后可能也會嘗試改下,現在沒仔細讀過那腳本- -;。

如果各位覺得自己的場景卡的話,不妨用上述方法一試,當然造成場景卡的原因很多,這只是其中之一,所以也別太期望會有很明顯的改善哦!不過這可以作為一個不錯的規范在項目之初就規范起來。

以上是官方文檔里提主要改善方法,其他一些優化我簡要說下吧,
在人物模型方面,他建議:1、模型必須用一個Mesh Renderer(2個的渲染時間就翻倍)
2、1個Mesh Renderer盡量少用多個materials,一般2-3個就夠了
3、每個模型使用30個左右的骨骼。

其他方面比如盡量不用像素光(Pixels Lights)啊、軟陰影啊這些基本都可以通過調參數來解決。

最后還是建議想全面優化下自己項目的朋友讀下官方文檔原文,還是會有很多收獲的。

以上,不設回復可見了,歡迎大家跟帖多討論和補充!

PS:感謝joeyzhang對本帖的補充,對於動態模型的聯結請看2樓

 

 

2樓------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

貼個可以綁定已經Skinned的動態物體的

/*IMPORTANT: READ !!!!!!
@Autor: Gabriel Santos
@Description Class that Combine Multilple SkinnedMeshes in just one Skinned Mesh Renderer
@Usage: Just AddComponent("CombineSkinnedMeshes") to your root component;
@IMPORTANT: This script uses the MeshCombineUtility Script provided by Unity

PS: It was tested with FBX files exported from 3D MAX
This Vertex Number and Bone Number must be configured according to your own AVATAR...
You can make a Counter to get the Number of Vertices from your imported character, I choice not do it since
this is script is just executed one time... */

using UnityEngine;
using System.Collections;


public class CombineSkinnedMeshes : MonoBehaviour {

        /// Usually rendering with triangle strips is faster.
        /// However when combining objects with very low triangle counts, it can be faster to use triangles.
        /// Best is to try out which value is faster in practice.
        public bool generateTriangleStrips = true;
        public bool castShadows = true;
        public bool receiveShadows = true;
        

    /* This is for very particular use, you must set it regarding to your Character */
        public static int VERTEX_NUMBER= 859; //The number of vertices total of the character
        public static int BONE_NUMBER =6;        //The number of bones total
        
    // Use this for initialization
    void Start () {
        
        
        //Getting all Skinned Renderer from Children
        Component[] allsmr = GetComponentsInChildren(typeof(SkinnedMeshRenderer));
        Matrix4x4 myTransform = transform.worldToLocalMatrix;
        Hashtable materialToMesh= new Hashtable();
        
        //The hash with the all bones references: it will be used for set up the BoneWeight Indexes
        Hashtable boneHash =new Hashtable();
        
        /* If you want make a counter in order to get the total of Vertices and Bones do it here ... */
        
        //The Sum of All Child Bones
        Transform[] totalBones = new Transform[BONE_NUMBER];//Total of Bones for my Example
        //The Sum of the BindPoses
        Matrix4x4[] totalBindPoses = new Matrix4x4[BONE_NUMBER];//Total of BindPoses
        //The Sum of BoneWeights
        BoneWeight[] totalBoneWeight = new BoneWeight[VERTEX_NUMBER];//total of Vertices for my Example
        
        
        int offset=0;
        int b_offset=0;
        Transform[] usedBones= new Transform[totalBones.Length];
        
        
        for(int i=0;i<allsmr.Length;i++)
        {
        //Getting one by one
            SkinnedMeshRenderer smrenderer  =  (SkinnedMeshRenderer)allsmr;
            //Making changes to the Skinned Renderer
            MeshCombineUtility.MeshInstance instance = new MeshCombineUtility.MeshInstance ();
            
            //Setting the Mesh for the instance
            instance.mesh = smrenderer.sharedMesh;
            
            if (smrenderer != null && smrenderer.enabled && instance.mesh != null) {                           
                                
                instance.transform = myTransform * smrenderer.transform.localToWorldMatrix;
               
                //Setting Materials
                Material[] materials = smrenderer.sharedMaterials;
               
                for (int m=0;m<materials.Length;m++) {
                    //Getting Minimum of SubMesh
                    instance.subMeshIndex = System.Math.Min(m, instance.mesh.subMeshCount - 1);  
               
                    //Setting Meshed Instances
                    ArrayList objects = (ArrayList)materialToMesh[materials[m]];
                    if (objects != null) {
                        objects.Add(instance);
                    }
                    else
                    {
                        objects = new ArrayList ();
                        objects.Add(instance);
                        materialToMesh.Add(materials[m], objects);
                    }
                }

                //Copying Bones
                for(int x=0;x<smrenderer.bones.Length;x++)
                {
                    
                bool flag = false;
                    for(int j=0;j<totalBones.Length;j++)
                    {
                        if(usedBones[j]!=null)
                            //If the bone was already inserted
                            if((smrenderer.bones[x]==usedBones[j]))
                            {
                                flag = true;
                                break;
                            }
                    }
                    
                    //If Bone is New ...
                    if(!flag)
                    {
                        //Debug.Log("Inserted bone:"+smrenderer.bones[x].name);
                        for(int f=0;f<totalBones.Length;f++)
                        {
                            //Insert bone at the firs free position
                            if(usedBones[f]==null)
                            {
                                usedBones[f] = smrenderer.bones[x];
                                break;
                            }
                        }
                        //inserting bones in totalBones
                        totalBones[offset]=smrenderer.bones[x];        
                        //Reference HashTable
                        boneHash.Add(smrenderer.bones[x].name,offset);
                        
                        //Recalculating BindPoses
                        //totalBindPoses[offset] = smrenderer.sharedMesh.bindposes[x] ;                        
                        totalBindPoses[offset] = smrenderer.bones[x].worldToLocalMatrix * transform.localToWorldMatrix ;
                        offset++;
                        
                    }
                    
                }
               
                //RecalculateBoneWeights
                for(int x=0;x<smrenderer.sharedMesh.boneWeights.Length ;x++)
                    {   
                        //Just Copying and changing the Bones Indexes !!                        
                        totalBoneWeight[b_offset] =  recalculateIndexes(smrenderer.sharedMesh.boneWeights[x],boneHash,smrenderer.bones);
                        b_offset++;
                    }
                //Disabling current SkinnedMeshRenderer
                ((SkinnedMeshRenderer)allsmr).enabled = false;
                    
            }
        
            
        }
        foreach (DictionaryEntry de  in materialToMesh)
        {
            ArrayList elements = (ArrayList)de.Value;
            MeshCombineUtility.MeshInstance[] instances = (MeshCombineUtility.MeshInstance[])elements.ToArray(typeof(MeshCombineUtility.MeshInstance));

            // We have a maximum of one material, so just attach the mesh to our own game object
            if (materialToMesh.Count == 1)
                {
                    
                    // Make sure we have a SkinnedMeshRenderer
                    if (GetComponent(typeof(SkinnedMeshRenderer)) == null)
                    {
                        gameObject.AddComponent(typeof(SkinnedMeshRenderer));
                    }
               
                    //Setting Skinned Renderer
                     SkinnedMeshRenderer objRenderer = (SkinnedMeshRenderer)GetComponent(typeof(SkinnedMeshRenderer));
                    objRenderer.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);                    
                    objRenderer.material = (Material)de.Key;
                    
                    objRenderer.castShadows = castShadows;
                    objRenderer.receiveShadows = receiveShadows;
                    
                    //Setting Bindposes
                    objRenderer.sharedMesh.bindposes = totalBindPoses;
                    
                    //Setting BoneWeights
                    objRenderer.sharedMesh.boneWeights = totalBoneWeight;
                    
                    //Setting bones
                    objRenderer.bones =totalBones;
                    
                    objRenderer.sharedMesh.RecalculateNormals();
                    objRenderer.sharedMesh.RecalculateBounds();
                    //Enable Mesh
                    objRenderer.enabled = true;                    

                    /* Debug.Log("############################################");
                    Debug.Log("bindPoses "+objRenderer.sharedMesh.bindposes.Length);
                    Debug.Log("boneWeights "+objRenderer.sharedMesh.boneWeights.Length);
                    Debug.Log("Bones "+objRenderer.bones.Length);
                    Debug.Log("Vertices "+objRenderer.sharedMesh.vertices.Length); */
            }
            // We have multiple materials to take care of, build one mesh / gameobject for each material
            // and parent it to this object
             else
            {
                GameObject go = new GameObject("CombinedSkinnedMesh");
                go.transform.parent = transform;
                go.transform.localScale = Vector3.one;
                go.transform.localRotation = Quaternion.identity;
                go.transform.localPosition = Vector3.zero;
                go.AddComponent(typeof(SkinnedMeshRenderer));
                ((SkinnedMeshRenderer)go.GetComponent(typeof(SkinnedMeshRenderer))).material = (Material)de.Key;

                SkinnedMeshRenderer objRenderer = (SkinnedMeshRenderer)go.GetComponent(typeof(SkinnedMeshRenderer));
                objRenderer.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
               
                //Setting Bindposes
                objRenderer.sharedMesh.bindposes = totalBindPoses;
                    
                //Setting BoneWeights
                objRenderer.sharedMesh.boneWeights = totalBoneWeight;
                    
                //Setting bones
                objRenderer.bones =totalBones;
                    
                objRenderer.sharedMesh.RecalculateNormals();
                objRenderer.sharedMesh.RecalculateBounds();
               
                //Enable Mesh
                objRenderer.enabled = true;
               
            }
        }
    }
   
    /*
    @autor: Gabriel Santos
    @Description: Revert the order of an array of components
    (NOT USED)
    */
    static Component[] revertComponent(Component[] comp )
    {
        Component[] result = new Component[comp.Length];
        int x=0;
        for(int i=comp.Length-1;i>=0;i--)
        {
                result[x++]=comp;
        }
        
        return result;
    }   
   
    /*
    @autor: Gabriel Santos
    @Description: Setting the Indexes for the new bones   
    */
    static BoneWeight recalculateIndexes(BoneWeight bw,Hashtable boneHash,Transform[] meshBones )
    {
        BoneWeight retBw = bw;
        retBw.boneIndex0 = (int)boneHash[meshBones[bw.boneIndex0].name];
        retBw.boneIndex1 = (int)boneHash[meshBones[bw.boneIndex1].name];
        retBw.boneIndex2 = (int)boneHash[meshBones[bw.boneIndex2].name];
        retBw.boneIndex3 = (int)boneHash[meshBones[bw.boneIndex3].name];
        return retBw;
    }
}

測試了上面的那個可以combine已經skinned的多部份模型的腳本,發現確實神奇,我的人物模型是由6部分組成(頭,上臂,下臂,身體,大腿,小腿和腳),原來加入骨骼動畫后演示是34drawcall(shader:diffuse,單一材質,開啟實時光照和陰影投射),經過該腳本合並后,只有14drawcall了。

實驗結果真實有效。。。。。

另外,使用該腳本有兩點要注意:
/* This is for very particular use, you must set it regarding to your Character */
                public static int VERTEX_NUMBER= 1708; //The number of vertices total of the character
                public static int BONE_NUMBER =35;                //The number of bones total

這里的頂點數和骨骼數目必須是你導入的模型的真實數目,如果你不知道這兩項具體數目,就把他們改成100000,然后開啟debug部分,系統會自動告訴你數目。
        Debug.Log("############################################");
                                        Debug.Log("bindPoses "+objRenderer.sharedMesh.bindposes.Length);
                                        Debug.Log("boneWeights "+objRenderer.sharedMesh.boneWeights.Length);
                                        Debug.Log("Bones "+objRenderer.bones.Length);
                                        Debug.Log("Vertices "+objRenderer.sharedMesh.vertices.Length);


免責聲明!

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



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