官方文档在这里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);