造成Unity游戏安装包大/运行卡的原因:
在我们开发一个移动端的游戏的话,可以直接使用移动端的本地的API来进行开发,也可以使用Unity引擎来进行开发,但是使用Unity引擎开发的话会比使用本地API开发来说应用会大,包也会大,运行卡,这是因为Unity为了使其能够跨平台内置了Mono跨平台虚拟机,所以我们开发的应用,写的程序都是在Mono虚拟机中运行的,所以打包出来后mono虚拟机也在其中,包会变大,并且是运行在Mono虚拟机里面的所以也会造成卡顿。
如果使用移动端API开发就是直接运行在移动端。包会小很多,但是这样去开发需要做很多工作,非常麻烦,使用unity的话会节省很多的时间,大大提高了效率,并且我们可以可以采用优化的方式去将我们的游戏包的大小和游戏的运行速率上去优化到用户可以接受到的一个程度。基于大大提高效率的情况下并且优化到一个可以接受的程度我们才会选择去使用Unity这个引擎制作游戏.
Draw Call
Draw Callshi什么呢?就是CPU对图形绘制接口的作用,CPU通过调用图形库接口,命令GPU进行渲染操作,下面通过两篇知乎去了解了解吧.
走近Draw Call
Unity中Draw Call和openGL、光栅化等有何内在联系,为什么说DC降低有助于渲染性能优化?
Draw Call调用大的话就会消耗大量的渲染性能,DrawCall过多,CPU就会很多额外开销用于准备工作,CPU本身负载,GPU就会闲置了,这样有可能就会造成游戏运行卡等问题。
所以我们在做性能优化的时候就需要去想方设法的去降低Draw Call.
在Unity里面打开Window->Profiler可以进行查看,然后选择Rendering运行程序这里可以查看到我的Draw Calls,里面还有一个Batches数量和Draw Call数量是一样的,通过一篇博文来学习学习
浅谈Draw Call和Batch的区别
场景越大,模型越多,角色越多,Draw Calls就会越大
Unity Statistics统计面板
【U3d】渲染统计窗口详细介绍(Rendering Statistics Window)
介绍完后下面来看看我们怎么去优化,总共的优化分为资源的优化,渲染的优化,代码的优化和其他的一些优化
(一)资源优化
(1)资源优化标准
Mesh动态模型:
面片数<3000
材质数<3
骨骼数<50
静态模型:顶点数<500
音乐资源优化 Audio长时间音乐(背景音乐)压缩格式 mp3 短时间音乐(音效)非压缩格式 wav
Texture贴图长款<1024
Shader尽量减少复杂数学运算
减少discard操作
(2)如何减少冗余资源和重复资源
A、Resources目录下的资源不管是否被引用,都会打包进安装包
不使用的资源不要放在Resources目录下
B、不同目录下的相同资源文件,如果都被引用,那么都会打包进资源包,造成冗余
C、保证同一个资源文件在项目中只存放在一个目录位置
资源监测与分析:https://www.uwa4d.com/#assetbundle
上面这个资源监测与分析主要是可以检测到我们AssetBundle安装包里面包含了些什么资源以及我们里面的冗余率(资源重复率)以及里面某些资源的利用率。可自行去了解
(二)渲染优化(GPU)
(1)CPU与GPU分工
CUP控制一些数值的运算,比如伤害值,随机数等,AI也是CPU运算出来的,GPU就是负责渲染图像的,包括特效各个图形等.
(2)LOD - 层级细节
层次细节(LOD),它是根据物体在游戏画面中所占视图的百分比来调用不同复杂度的模型的。简单而言,就是当一个物体距离摄像机比较远的时候使用低模,当物体距离摄像机比较近的时候使用高模。这是一种优化游戏渲染效率的常用方法,缺点是占用大量内存。使用这个技术,一般是在解决运行时流畅度的问题,采用的是空间换时间的方式。
例如我这有三个油桶模型,一个比一个精细,一个比一个面多。我们可以当我们视野在很远的时候放入第一个很粗糙的模型。视野再近一些放入第二个稍精细的模型,再靠近就放入第三个最精细的模型。这样可以以空间换时间的方式来减少渲染的面。达到一个渲染的优化
在Unity里面创建一个空对象,给其添加一个LOD Group的组件
然后我们把第最精细的模型给LOD0,稍精细的给LOD1,最次的模型给LOD2就行了。接下来我们在场景中就可以拉近拉远距离来观察了,当离的比较近的时候是LOD0
拉远一些就是LOD1
再拉远一下就是LOD2 当然拉到最远就看不到所以LOD012都不会显示了。这就是我们的LOD技术
(3)遮挡剔除
比如首先创建一排箱子。
然后我们让摄像机看不到的地方的物体让其隐藏,首先选择所有的箱子把它们的Static选择为Occluder Static。
然后选择Window->Occlusion Culling
摄像机选择我们的Main Camera最后烘培一下就OK了。
这样我们来看看场景,这样就完成了物体只在视野内才显示,这样就大大降低了渲染的大小。
如果想知道更多可以去了解: https://docs.unity3d.com/Manual/OcclusionCulling.html
(4)光照贴图
光照烘培指我们在打了灯光后烘培后,使用了LightMaping后整个光照就不需要时时去计算,就在最开始的时候计算好并制作成贴图,后面就直接用这个光照后的贴图了。通过贴图取得光照的效果。那么我们怎么去做呢?首先我们先搭建一个场景,可以看到Draw Calls是375
下面我们去烘培一个光照贴图,首先我们选择我们要烘培的物体箱子和墙将他们的Static设置为Lightmap Static
然后将所有灯光的Mode选择为Baked的模式。
最后选择Window->Lighting->Settings里面吧Auto自动生成取消后点击烘培
烘培后就可以在Assets里面看到一些烘培出来的光照贴图了,这时候就可以把所有的灯光都去掉可以发现场景中依然有灯光的效果。然后我们运行程序可以看到这里的Draw Calls变成了79,直接少了很多。
这样就完成了光照渲染的一个优化
(5)MeshFilter的合并
首先搭建一个简单的场景,然后创建一个空物体,让所有的场景模型都成为这个空物体的子物体,然后在这个空物体下创建两个组件MeshFilter和MeshRenderer并给MeshRenderer贴上材质,然后在创建一个脚本挂上面。在脚本里面我们去合并MeshFilter并给父物体.
using UnityEngine;
public class MeshCombiner : MonoBehaviour {
// Use this for initialization
void Start () {
MeshCombine();
}
void MeshCombine()
{
MeshFilter[] filters = GetComponentsInChildren();//获取所有子物体的MeshFilter
CombineInstance[] combiners = new CombineInstance[filters.Length];//CombineInstance合并的一个实例
//把filters里面的MeshFilter设置给combiners
for (int i = 0; i < filters.Length; i++)
{
combiners[i].mesh = filters[i].sharedMesh;
//矩阵 把局部坐标转换成世界坐标给combiners
combiners[i].transform = filters[i].transform.localToWorldMatrix;
}
Mesh finalMesh = new Mesh();//合并后的Mesh
finalMesh.CombineMeshes(combiners);//CombineMesh合并
//将合并的MeshFilter给了父物体的MeshFilter
GetComponent().sharedMesh = finalMesh;
//获取所有子物体,让其隐藏
foreach (Transform child in transform)
{
child.gameObject.SetActive(false);
}
}
}
最后我们运行就可以看到子物体所有的MeshFilter都合并给了父物体,而子物体全部都隐藏了,这样的话也会减小一些Draw Calls
(三)代码优化(CPU)
(1)
To render objects on the screen, the CPU has a lot of processing work to do: working out which lights
affect that object, setting up the shader and shader parameters, and sending drawing commands to the
graphics driver, which then prepares the commands to be sent off to the graphics card.
要在屏幕上渲染对象,CPU有很多处理工作要做:比如要做出哪些灯
去影响该对象,设置着色器和着色器参数,并向其发送绘图命令
图形驱动程序,然后准备要发送到图形卡的命令给GPU。
All this “per object” CPU usage is resource-intensive, so if you have lots of visible objects, it can add up.
For example, if you have a thousand triangles, it is much easier on the CPU if they are all in one mesh,
rather than in one mesh per triangle (adding up to 1000 meshes). The cost of both scenarios on the GPU
is very similar, but the work done by the CPU to render a thousand objects (instead of one) is
significantly higher.
所有这些“每个对象”的CPU使用情况都是资源密集型的,所以如果你有很多可见对象,它可以相加。
例如,如果您有一千个三角形,那么如果CPU全部在合并到一个网格中,那么它将更容易效率更好(只需要准备计算并发送一次命令),
而不是每个三角形都有一个网格(最多1000个网格)全部一起发送过去,(这样的话需要做很多准备工作,非常消耗CPU的性能)。 GPU上的两种情况的成本
都是一样的(都是需要渲染1000个网格),但是CPU所做的工作就是计算发送一千个渲染对象命令(而不是一个)
明显更高。
Reduce the visible object count. To reduce the amount of work the CPU needs to do:
· Combine close objects together, either manually or using Unity’s draw call batching.
· Use fewer materials in your objects by putting separate textures into a larger texture atlas.
· Use fewer things that cause objects to be rendered multiple times (such as reflections, shadows
and per-pixel lights).
为了减少可见物体数。为了减少CPU需要做的工作量:
·将对象组合在一起,手动或使用Unity的绘制调用批处理合并。
·通过将单独的纹理贴图放入一个大的纹理图集中,在对象中使用较少的材质。
·使用较少的事物导致对象多次渲染(如反射,阴影和光照分辨率等一些技术)。
Combine objects together so that each mesh has at least several hundred triangles and uses only one
Material for the entire mesh. Note that combining two objects which don’t share a material does not give
you any performance increase at all. The most common reason for requiring multiple materials is that two
meshes don’t share the same textures; to optimize CPU performance, ensure that any objects you
combine share the same textures.
将对象组合在一起,使每个网格至少有几百个三角形,并且仅使用一个
整个网格的材质。注意,组合两个不共享资料的对象不给出
你的任何表现都会增加。需要多种材料的最常见原因是两种
网格不共享相同的纹理;优化CPU性能,确保任何对象
组合共享相同的纹理。
(2)资源池 - Object Pooling
比如我们在做FPS游戏的时候每次发射子弹又销毁子弹是很耗性能的,这时候我们就可以使用资源池把创建的子弹保存到List里面,发射子弹的时候设SetActive设置为 True,销毁的时候设置为False,这样就不需要每次都去创建子弹了。下面直接上代码
using System.Collections.Generic;
using UnityEngine;
//资源池
public class BulletPool : MonoBehaviour {
public int poolCount = 30;//保存30颗子弹
public GameObject bulletPrefab;
private List bulletList = new List();
// Use this for initialization
void Start () {
InitPool();
}
// Update is called once per frame
void Update () {
}
//初始化 ,创建子弹
void InitPool()
{
for (int i = 0; i < poolCount; i++)
{
GameObject go = GameObject.Instantiate(bulletPrefab, transform.position, transform.rotation);
bulletList.Add(go);
go.SetActive(false);
go.transform.parent = this.transform;
}
}
//去bulletList里面取得实例化的子弹
public GameObject GetBullet()
{
foreach (GameObject go in bulletList)
{
if (go.activeInHierarchy == false)
{
go.SetActive(true);
return go;
}
}
return null;
}
}
下面要用的时候直接去池子里面找没用过的子弹发射就行了,并且在销毁的时候设置SetActive设置为False就行了。
using System.Collections;
using UnityEngine;
public class Shoot : MonoBehaviour {
public GameObject bulletPrefab;
private BulletPool bulletpool;
// Use this for initialization
void Start() {
bulletpool = GetComponent();
}
// Update is called once per frame
void Update() {
if (Input.GetMouseButtonDown(0))
{
GameObject go = bulletpool.GetBullet();
go.transform.position = transform.position;
go.GetComponent().velocity = transform.forward * 50;
StartCoroutine(DestroyBullet(go));
}
}
//销毁隐藏子弹还是在资源池里面
IEnumerator DestroyBullet(GameObject go)
{
yield return new WaitForSeconds(3);
go.SetActive(false);
}
}
具体的细节可自行去编写
这个资源池适用于游戏当中任何地方,只有这个地方需要进行频繁实例化,频繁的销毁,这时候就可以使用资源池去管理,比如同一类型的敌人,不同类型的敌人也可以用资源池去管理,比如可用一个字典,字典里面放LIst,这样就可以管理多个类型的敌人,这样可以成为一个大的资源池
(四)其他优化
使用Unity开发安卓游戏 应该如何进行性能优化
音效优化:https://www.jianshu.com/p/59b883c0bbb1
编译性能的优化:
优化Unity项目编译速度
让unity的编译速度在快一些