一、什么是DrawCall
(一)DrawCall:就是CPU对图形绘制接口的调用,CPU通过调用图形库(DirectX/Opengl)接口,命令GPU进行渲染操作;
(二)问:DrawCall是如何影响性能的?
答:每一次绘制CPU都要调用DrawCall,而在调动DrawCall前,CPU还要进行很多准备工作:检测渲染状态、提交渲染所需要的数据、提交渲染所需要的状态。而GPU本身具有很强大的计算能力,可以很快就处理完渲染任务。当DrawCall过多,CPU就会很多额外开销用于准备工作,CPU本身负载,而这时GPU可能闲置了。
做个试验:拷贝1000个总大小1M的文件和单个大小为1M的文件,明显拷贝1000个文件要慢很多,DrawCall调用和这个很类似。
二、Unity Statistics统计面板
全称叫做 Rendering Statistics Window,即渲染统计窗口(或渲染数据统计窗口),窗口中罗列出关于声音、图像、网络状况等多种统计信息。
★FPS(Time per frame andFPS): frames per seconds表示引擎处理和渲染一个游戏帧所花费的时间,该数字主要受到场景中渲染物体数量和 GPU性能的影响,FPS数值越高,游戏场景的动画显示会更加平滑和流畅。一般来说,超过30FPS的画面人眼不会感觉到卡,由于视觉残留的特性,光在视网膜上停止总用后人眼还会保持1/24秒左右的时间,因此游戏画面每秒帧数至少要保证在30以上。另外,Unity中的FPS数值仅包括此游戏Scene里更新和渲染的帧,编辑器中绘制的Scene和其它监视窗口的进程不包括在内;
★CPU:获取到当前占用CPU进行计算的时间绝对值,或时间点,如果Unity主进程处于挂断或休眠状态时,CPU time将会保持不变;
★Render thread: GPU渲染线程处理图像所花费的时间,具体数值由GPU性能来决定;
★Batches: 即Batched Draw Calls,是Unity内置的Draw Call Batching技术;
解释:CPU每次通知GPU发出一个glDrawElements(OpenGl中的图元渲染函数)或者 DrawIndexedPrimitive(DirectX中的顶点绘制方法)的过程称为一次Draw call,一般来说,引擎每对一个物体进行一次DrawCall,就会产生一个Batch,这个Batch里包含着该物体所有的网格和顶点数据,当渲染另一个相同的物体时,引擎会直接调用Batch里的信息,将相关顶点数据直接送到GPU,从而让渲染过程更加高效,即Batching技术是将所有材质相近的物体进行合并渲染。
对于含有多个不同Shader和Material的物体,渲染的过程比较耗时,因为会产生多个Batches。每次对物体的材质或者贴图进行修改,都会影响Batches里数据集的构成。因此,如果场景中有大量材质不同的物体,会很明显的影响到GPU的渲染效率。这里说几点关于Batches优化相关的方案:
- 虽然Unity引擎自带Draw Call Batching技术,我们也可以通过手动的方式合并材质接近的物体;
- 尽量不要修改Batches里物体的Scale,因为这样会生成新的Batch。
- 为了提升GPU的渲染效率,应当尽可能的在一个物体上使用较少的材质,减少Batches过多的开销;
- 对于场景中不会运动的物体,考虑设置Static属性,Static声明的物体会自动进行内部批处理优化。
★Verts:摄像机视野(field of view)内渲染的顶点总数。
★Tris: 摄像机视野(field of view)内渲染的的三角面总数量。
解释:Camera的渲染性能受到Draw calls的影响。之前说过,对一个物体进行渲染,会生成相应的Draw call,处理一个Draw Call的时间是由它上边的Tris和Verts数目决定。尽可能得合并物体,会很大程度的提高性能。举个很简单例子,比如场景一种有1000个不同的物体,每个物体都有10个Tris;场景二中有10个不同的物体,每个物体有1000个Tris。在渲染处理中,场景一中会产生1000个Draw Calls,它的渲染时间明显比场景二慢。
Unity stats 视图中的 Tris 和 Verts 并不仅仅是视锥中的梯形内的 Tris 和 Verts,而是Camera中 field of view所有取值下的tris和verts,换句话说,哪怕你在当前game视图中看不到这个 cube,如果当你把 field of view调大到 179 过程中都看不到这个cube,stats面板才不会统计,GPU才不会渲染,否则都会渲染,而且unity不会把模型拆分,这个模型哪怕只有1个顶点需要渲染,unity也会把整个模型都渲出来。
★Screen: 获当前Game屏幕的分辨率大小,后边的2.1MB表示总的内存使用数值;
解释:场景上有1个gameobject,希望能显示很酷炫的效果,它的Material上带有许多特定的Shader。为了实现相应的效果,Shader里或许会包含很多的Pass,每当GPU即将去运行一个Pass之前,就会产生一个“SetPass call”,因此在描述渲染性能开销上,“SetPass calls”更加有说服力。
★Shadow casters:表示场景中有多少个可以投射阴影的物体,一般这些物体都作为场景中的光源;
★visible skinned meshed:渲染皮肤网格的数量;
★Animations: 正在播放动画的数量;
三、资源优化标准(移动端)
(一)Mesh
1.动态模型:面片数<3000、材质数<3、骨骼数<50;
2.静态模型:顶点数<500;
(二)Audio
1.长时间音乐(背景音乐)用压缩格式mp3;
2.短时间音乐(音效)用非压缩格式wav;
(三)Texture
贴图长宽<1024;
(四)Shader
1.尽量减少复杂数学运算;
2.减少discard操作;
四、减少冗余重复资源
(一)Resources目录下的资源不管是否被引用,都会打包进安装包,所以,不使用的资源请不要放在Resources目录下;
(二)不同目录下的相同资源文件,如果都被引用,那么都会打包进资源包,造成冗余,所以,请保证同一个资源文件在项目中只存放在一个目录位置;
五、资源监测与分析
UWA在线AssetBundle检测:https://www.uwa4d.com/#assetbundle
六、LOD(层次的细节)
建三个不同精度的同种模型,然后放在一个空物体下,空物体上添加LOD Group组件,然后将高精度模型拖入LOD0,中精度模型拖入LOD1,低精度模型拖入LOD2,然后可调节三个区域所占的大小;
这个其实也没啥好讲的,略略略!
七、遮挡剔除OcclusionCulling
(一)视锥体剔除(Frustum Culling)
根据摄像机的视见体的范围对场景模型进行剔除操作,在视见体以外的物体不被渲染,但是在视见体中的物体会以离摄像机最远的物体开始渲染,逐渐渲染靠近摄像机的物体。后渲染的物体会覆盖先前渲染的物体。
锥体剔除只剔除摄像机视角范围外的物体而对于被包含在视见体中的其他对象还是会进行渲染,即摄像机看不到的游戏对象也会进行渲染。
(二)遮挡剔除(Occlusion Culling)
剔除视见体以外的游戏对象,并且剔除视见体内被其他游戏对象所遮住的物体。
1. 静态物体的遮挡剔除:
将需要自动遮挡的对象勾选 为Occluder Static/Occludee Static,然后在 Occlusion 中进行简单的设置 Bake 即可。最后可以在 Visualization 模式下进行测试。
Occluder Static: 需要进行遮挡剔除操作的对象勾选;
Occludee Static: 进行遮挡剔除操作的对象为透明或半透明,以及那些不太可能遮挡其他物体的小物体。即能被其他物体遮挡而本身却不会遮挡到其他物体,这将有效减少计算量。
Smallest Occluder: 该值越小,烘焙的效果越好越精确;
Smallest Hole: 最小的洞;
Visualization : 形象化。在测遮挡剔除效果时选择此项(需要选择一个摄像机)。
2. 动态物体的遮挡剔除:
(1)为运动物体的运动范围添加遮挡区域 ,即添加组件 Occlusion Area 进行设置;
(2)将其他静态对象勾选为遮挡剔除静态物体,然后在 Occlution 窗口中进行设置烘焙即可;
3. 入口遮挡:
为了创建可实时开启和关闭的遮挡。上面的静态、动态物体遮挡为引擎自动遮挡,而入口遮挡为人工控制的遮挡(脚本控制)。如下图:
(1)给门添加 Occlusion Portal 组件,不勾选 Open 属性。即门后的物体不渲染;
(2)Bake 其他静态物体;
(3)添加脚本控制:
/**
*
* 项目: 遮挡剔除
*
* 功能: Occlusion Portal 的简单应用
*
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OcclusionPortalPractice : MonoBehaviour
{
private OcclusionPortal _OcclusionPortal; //遮挡入口
private Renderer _Renderer; //渲染器
private void Start()
{
_OcclusionPortal = GetComponent<OcclusionPortal>();
_Renderer = GetComponent<Renderer>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
_OcclusionPortal.open = true; //渲染门后面的内容
_Renderer.enabled = false; //不渲染门
}
if (Input.GetKeyDown(KeyCode.D))
{
_OcclusionPortal.open = false; //不渲染门后面的内容
_Renderer.enabled = true; //渲染门
}
}
}
八、光照贴图LightMapping
场景烘焙上,静态物体勾选完static、场景设置完灯光baked和光照探针之后,我一般都是这么设置的:
九、资源池——ObjectPool技术
举例:子弹射击,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletPool : MonoBehaviour {
public int poolCount = 30;
public GameObject bulletPrefab;
private List<GameObject> bulletList = new List<GameObject>();
private void Start()
{
InitPool();
}
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;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour {
public GameObject bulletPrefab;
private BulletPool bulletPool;
void Start () {
bulletPool = GetComponent<BulletPool>();
}
void Update () {
if (Input.GetMouseButtonDown(0))
{
//GameObject go = GameObject.Instantiate(bulletPrefab,transform.position,transform.rotation);
GameObject go = bulletPool.GetBullet();
go.transform.position = transform.position;
go.GetComponent<Rigidbody>().velocity = transform.forward * 50;
//Destroy(go, 3);
StartCoroutine(DestroyBullet(go));
}
}
IEnumerator DestroyBullet(GameObject go)
{
yield return new WaitForSeconds(3);
go.SetActive(false);
}
}
十、其他优化技巧
(一)提高Unity编译速度的小技巧:将基本上不再需要变更的代码放入Plugins文件夹下;(注意:是以后基本不需要变更的代码)。