优化相关前提
-
Unity游戏安装包大/运行卡的原因:
mono虚拟机
(跨平台基础、简洁开发提高效率) -
Drawcall
- https://zhuanlan.zhihu.com/p/26386905
- https://www.zhihu.com/question/36357893
- SetPasscall DrawCall和Batches
http://www.manew.com/4702.html
资源优化
-
资源优化参考标准
- Mesh
动态模型:面片数<3000 、材质数<3 、骨骼数<50
静态模型:顶点数<500 - Audio
长时间音乐(背景音乐)压缩格式 mp3
短时间音乐(音效)非压缩格式 wav
http://blog.youkuaiyun.com/u012565990/article/details/51794486 - Texture
贴图长宽<1024 - Shader
尽量减少复杂数学运算
减少discard操作
- Mesh
-
模型优化
减少面数和顶点数 -
贴图优化
贴图合并,减少DrawCall的调用次数 -
减少冗余资源和重复资源
- Resources 目录下的资源不管是否被引用,都会打包进安装包
不使用的资源不要放在Resources目录下 - 不同目录下的相同资源文件,如果都被引用,那么都会打包进资源包,造成冗余
保证同一个资源文件在项目中只存放在一个目录位置
- Resources 目录下的资源不管是否被引用,都会打包进安装包
-
资源监测与分析
https://www.uwa4d.com/#assetbundle
腾讯、UWA 进行性能检测和优化
渲染优化(GPU)
- CPU GPU分工
https://www.zhihu.com/question/21475727 - LOD - 层级细节
在LOD2和LOD1可以修改贴图的品质,Cast Shadow和 ReceiveShadow可以移除 - Occlusion Culling - 遮挡剔除
- Lightmapping - 光照贴图
- 合并Mesh
using UnityEngine;
using UnityEditor;
public class MeshCombiner : MonoBehaviour
{
//验证函数
[MenuItem("Tool/MeshCombine", true, 10)]
static bool MeshCombineValidate()
{
if (Selection.gameObjects.Length > 0)
return true;
else
return false;
}
/// <summary>
/// 给hierachy窗口中的物体添加一个右键选项
/// 合并物体Mesh的方法MeshCombine()
///
/// 使用须知:需要将要合并mesh的物体全都放在一个空物体下
/// </summary>
[MenuItem("Tool/MeshCombine", false, 10)]
public static void MeshCombine()
{
//获取当前选中物体
foreach (GameObject go in Selection.gameObjects)
{
//记录当前位置iveGameObje
Vector3 goPosition = go.transform.position;
//避免生成新物体时产生位置偏移
go.transform.position = Vector3.zero;
//获取子物体的所有mesh
MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>();
//创建mesh合并的实例
CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length];
//将meshFilters的mesh设置给combineInstances
for (int j = 0; j < meshFilters.Length; j++)
{
//sharedMesh是公用的,是引用传递。而mesh是值传递,是各自拥有的实例。sharedMesh改变,则所有的使用到此mesh的都改变。
combineInstances[j].mesh = meshFilters[j].sharedMesh;
//将meshFilters[i]的局部转世界坐标的矩阵给combineInstances[i].transform,从而确定mesh在世界空间的坐标
combineInstances[j].transform = meshFilters[j].transform.localToWorldMatrix;
}
//实例化一个新的mesh来获得合并出来的mesh
Mesh mesh = new Mesh();
mesh.CombineMeshes(combineInstances); //将combineInstances合并成一张mesh
mesh.name = go.name; //设置mesh显示的name
//生成一个名字相同的物体,添加MeshRenderer和MeshFilter组件
GameObject gameObject = new GameObject(mesh.name, new System.Type[] { typeof(MeshRenderer), typeof(MeshFilter) });
//将物体的生成注册到操作记录中
Undo.RegisterCreatedObjectUndo(gameObject, "Create Gameobject");
gameObject.transform.position = goPosition;
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
//将合并出来的mesh给新物体的MeshFilter
meshFilter.sharedMesh = mesh;
//实例化一个黑色材质给这个物体
Material material = new Material(Shader.Find("Standard"))
{
name = gameObject.name + " " + "Material",
color = Color.black
};
gameObject.GetComponent<Renderer>().material = material;
//回到原来位置
go.transform.position = goPosition;
Undo.DestroyObjectImmediate(go.gameObject);
}
}
}
代码优化(CPU)
- https://drive.google.com/file/d/0B__1zp7jwQOKcHJIM3N2TklqdEU/view?pageId=107075542061258892781
- 对象资源池 - Object Pooling
Shoot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public GameObject bulletPrefab;
private BulletPool bulletPool;
// Start is called before the first frame update
void Start()
{
bulletPool = GetComponent<BulletPool>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// GameObject go = GameObject.Instantiate(bulletPrefab, transform.position, transform.rotation );
GameObject go = bulletPool.GetBullet();
if (go != null)
{
go.transform.position = transform.position;
go.GetComponent<Rigidbody>().velocity = transform.forward * 10;
//Destroy(go, 3);
StartCoroutine(DestroyBullet(go));
}
}
}
IEnumerator DestroyBullet(GameObject go)
{
yield return new WaitForSeconds(3);
go.SetActive(false);
}
}
BulletPool.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletPool : MonoBehaviour
{
public int poolCount = 10;
public GameObject bulletPrefab;
private List<GameObject> bulletList = new List<GameObject>();
void InitPool()
{
for (int i = 0; i < poolCount; i++)
{
GameObject go = GameObject.Instantiate(bulletPrefab);
bulletList.Add(go);
go.SetActive(false);
go.transform.parent = this.transform;
}
}
public GameObject GetBullet()
{
foreach (GameObject go in bulletList)
{
if (go.activeInHierarchy == false)
{
go.SetActive(true);
return go;
}
}
return null;
}
// Start is called before the first frame update
void Start()
{
InitPool();
}
}
此对象池过于简单,实用性更强可参照对象池模式。
带宽优化
- 减少纹理大小
- 利用缩放
其他优化
- 优化工具
UWA Game Optimization Toolkit - 文章
使用Unity开发安卓游戏 应该如何进行性能优化
http://gad.qq.com/article/detail/17252 - 编译性能优化
http://gad.qq.com/article/detail/27927
http://forum.china.unity3d.com/thread-13028-1-1.html
问题
-
Stats中的render thread(渲染时间)和FPS(帧率)之间的关系
假设渲染时间 render thread 为 0.4ms ,是否在渲染结束每帧之后会有空余时间,那么不考虑硬件支持,极致帧率应为 1000/0.4=2500FPS 么? -
创建一个 cube , draw calls 和 batches 增加5 , 删除掉,在创建一个 sphere , draw calls 和 batches 也是增加5。为什么创建一个 cube 不删除,在创建一个 sphere , draw calls 和 batches 只增加了9 ? 同时增加两个物体, draw calls 和 batches 只增加了8?
-
CPU和GPU都负责计算,为什么要用GPU负责渲染而不用CPU?
CPU(中央处理器,Central Processing Unit)运行的是复杂指令,可以进行各种运算,所谓样样精样样松。
而GPU(图形处理器,Graphic Processing Unit)指令集简单,工程师就可以将大部分晶体管投入数据运算,所以GPU在图形处理方面要比CPU快很多,一个专门的图形的核心处理器。
显卡为了加强显示图像的能力,省掉了很多与显示功能无关的晶体管,所以显卡只能显示画面,但是在显示画面这个任务上,比cpu快很多。 -
合并mesh之后,子物体的材质是不能应用于合并出来的物体的,怎么办?合并材质贴图?