不一样的视角,深度解读unity性能优化。unity性能优化,unity内存优化,cpu优化,gpu优化,资源优化,资源包、资源去重优化,ugui优化。
- gpu优化
- 静态批处理
- 静态批处理原理规则
- 静态合批的原理
- 静态合批的必须条件
- 结论与总结
- 静态合批原则与优化核心
- 动态批处理
- srpBach
- Gpu实例化
- 蒙皮实例化
gpu优化
静态批处理
静态批处理原理规则
静态批处理官方文档
静态合批的原理
静态批处理是场景中将同一个材质(属性完全相同的同一个材质,可以是不同网格)的不同变换(位移,旋转,缩放)网格合并成一个大的网格。然后在调用drawcall绑定shader设置shader状态和绑定网格到Gpu,进而一次完成渲染一个大网格实现渲染场景多个相同网格的优化
基础条件
- GameObject 是活动的。
- GameObject 具有启用的 Mesh Filter 组件。
- Mesh Filter 组件引用一个 Mesh。
- Mesh 启用了读/写。
- Mesh 的顶点数大于 0。
- Mesh 没有与其他 Mesh 组合过。
- GameObject 具有启用的 Mesh Renderer 组件。
- Mesh Renderer 组件未使用具有 DisableBatching 标签设置为 true 的材质。
- Camera.main.opaqueSortMode这个对静态合批的影响很大
静态合批的必须条件
- 网格使用同一个材质,如果通过Instance或者其它方法获取的材质即使属性完全相同,也不是同一个材质。除非所有相同网格都使用这个实例化后的材质,才是同一材质
- 然后就是shader没有设置DisableBatching 标签设置为 true
- Mesh 启用了读/写,值得一提的是当你把GameObject的Static Editor Flags 中启用 Batching Static时,运行unity后,unity会自动将网格设置为可读状态
注意:静态批处理对顶点数量有上限。每个静态批次最多可以包含 64000 个顶点。如果超过此数量,Unity 会创建另一个批次。
为了测试静态批处理首先编写一个测试脚本
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
public class GreenCtrl : MonoBehaviour
{
[Serializable]
public class MeshInfo
{
public MeshInfo(Mesh mesh)
{
CurMesh = mesh;
// Vertex = mesh.vertices.Length;
Name = mesh.name;
}
public string Name;
public Mesh CurMesh;
public int Vertex;
public override int GetHashCode()
{
return CurMesh.name.GetHashCode();
}
}
[SerializeField] private Transform[] greens;
[SerializeField] private Transform greensParent;
public int count = 2000;
public int size = 100;
public float localScale = 10;
public GameObject[] instances;
public bool isGpuInstance = false;
public Mesh[] meshes;
// Start is called before the first frame update
void Start()
{
if (isGpuInstance)
{
Gen();
InitData();
}
Camera.main.opaqueSortMode = UnityEngine.Rendering.OpaqueSortMode.Default;
MargeMesh();
}
void MargeMesh()
{
StaticBatchingUtility.Combine(instances, greensParent.gameObject);
}
public void CloseSort()
{
Camera.main.opaqueSortMode = UnityEngine.Rendering.OpaqueSortMode.NoDistanceSort;
}
public void SetMesh()
{
HashSet<Mesh> meshHash = new HashSet<Mesh>();
meshes = Array.ConvertAll(instances, obj => obj.GetComponentInChildren<MeshFilter>().sharedMesh);
HashSet<string> names = new HashSet<string>();
for (int i = 0; i < instances.Length; i++)
{
Mesh mesh = meshes[i];
List<Vector3> v = new List<Vector3>();
meshHash.Add(mesh);
names.Add(mesh.name);
}
meshes = meshHash.ToArray();
Array.Sort(meshes, (m1, m2) => GetIndex(m1.name).CompareTo(GetIndex(m2.name)));
Debug.LogError($"所有网格总数:{meshHash.Count}====={names.Count}");
}
int GetIndex(string name)
{
int index = 0;
int.TryParse(name.Substring("Combined Mesh (root:scene) ".Length), out index);
return index;
}
// Update is called once per frame
void Update()
{
if (isGpuInstance)
{
ResetDraw();
}
}
public void Gen()
{
while (greensParent.childCount > 0)
{
DestroyImmediate(greensParent.GetChild(0).gameObject);
}
instances = new GameObject[count];
for (int i = 0; i < count; i++)
{
Vector2 pos = Random.insideUnitCircle; //随机一个圆内的位置出来
pos *= size;
// float x = Mathf.Abs(pos.x);//把随机出来的位置固定到第一象限
// float y = Mathf.Abs(pos.y);
int index = (int)Mathf.Lerp(0, greens.Length, Random.value);
Transform trans = greens[index];
trans = Instantiate(trans, greensParent);
trans.localPosition = new Vector3(pos.x, Mathf.Lerp(0, 2, Random.value), pos.y);
trans.localScale = Vector3.one * localScale;
instances[i] = trans.gameObject;
}
InitObj();
}
private MaterialPropertyBlock materialPropertyBlock;
void InitObj()
{
materialPropertyBlock = new MaterialPropertyBlock();
MeshRenderer renderer;
foreach (GameObject obj in instances)
{
// 为每个实例设置一个随机颜色
float r = Random.Range(0f, 1f);
float g = Random.Range(0f, 1f);
float b = Random.Range(0f, 1f);
Color randomColor = new Color(r, g, b);
// 设置属性块中的颜色
// materialPropertyBlock.SetColor("_Color", randomColor);
// 获取渲染器并应用属性块
renderer = obj.GetComponent<MeshRenderer>();
// renderer.SetPropertyBlock(materialPropertyBlock);
}
}
public void GenGpuInstance()
{
for (int i = 0; i < count; i++)
{
Vector2 pos = Random.insideUnitCircle; //随机一个圆内的位置出来
pos *= size;
int index = (int)Mathf.Lerp(0, greens.Length, Random.value);
Transform trans = greens[index];
trans = Instantiate(trans, greensParent);
trans.localPosition = new Vector3(pos.x, Mathf.Lerp(0, 2, Random.value), pos.y);
trans.localScale = Vector3.one * localScale * Random.value;
instances[i] = trans.gameObject;
}
InitObj();
}
private GraphicsDraw CurGraphicsDraw = new GraphicsDraw();
public void InitData()
{
CurGraphicsDraw.InitDrawData(instances);
}
public void ResetDraw()
{
//CurGraphicsDraw.DrawGrass();
CurGraphicsDraw.DrawGrassGpuInstance();
}
}
编写此类的编辑器脚本,放在Editor文件夹下
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(GreenCtrl))]
public class GreenCtrlEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("生成植被"))
{
((GreenCtrl)target).Gen();
}
if (GUILayout.Button("动态合批"))
{
((GreenCtrl)target).SetMesh();
}
if (GUILayout.Button("设置网格"))
{
((GreenCtrl)target).SetMesh();
}
if (GUILayout.Button("关闭渲染顺序"))
{
((GreenCtrl)target).CloseSort();
}
}
}
挂载设置脚本,将对象设置成静态合批Batching Static,以确保生成的对象也是静态批处理,设置父节点,草模型,生成数量Count为100
注意的是Greens是草模型
点击生成植被,我设置的生成100个草模型,没有超过64000个顶点
打开FrameDebugger
可看到植被生成217-2=215个批处理。
点击运行,启动静态批处理
这是你会发现所有相同材质的网格都合并成一个批次,就是说所有相同材质草模型合并成一个大网格使用同一个材质调用一个drawcall完成了多个模型的渲染。并且静态合批的名称为Static Batch
继续增加草的数量为6000,生成草区域Size调到250,调整一下摄像机的位置
现在草的静态合批为192-2=190个批次。理论上应该生成1个批次,因为同一个材质会合并为同一批次。为什么会生成这么多个批次呢?
因为静态合批的合并定点数最多64000个顶点,所以超过这个顶点数就会把剩下的网格合并到另一个批次。
那么如何查看合并后的网格信息呢
选中一个草模型对象,在Mesh参数中,当启动游戏后,Mesh就会自动刷新成合批后的网格
未运行前的网格
运行后的静态合批网格
你会发现合批后网格都会刷到每一个草模型的Mesh,并且网格名字都是以Combined Mesh (root:Scene)为前缀的网格。
双击网格查看网格信息
你会发现红色框模型顶点总数接近64000个顶点,但不会超过这个数。
同时绿框为合并网格后的一个个子网格信息
但是我发现所有草合并后的网格只有46个.理论上应该只有46个合批,为什么变成190个合批呢。。。
这一步测试如下
点击“设置网格”
此功能会把所有不同网格保存起来。会发现确实只有46个合成的大网格。那么为什么多生成这么多合批呢?
这是因为合批还会根据相机的Camera.main.opaqueSortMode渲染顺序模式有关
点击关闭渲染顺序
点击Disable两次,刷新数据
会发现现在合批会非常接近46了。至于为什么还会多出7个。应该是渲染的其它因素影响。但是到这也差不多把影响合批的原理逻辑基本说明了。
Camera.main.opaqueSortMode这个对静态合批的影响很大。即使正常合并网格,Camera.main.opaqueSortMode也会对静态合批影响很大。
现在将草的模型添加多个,并且这些草使用同一材质,关闭渲染顺序
你会发下增减了7中同一材质的不同模型,静态合批数量也只增了1倍。
结论与总结
静态合批原则与优化核心
- 将场景模型使用同一个材质,并使用上面同一个图集。保证所有模型使用相同材质
- 将场景所有不同模型通过工具或者脚本把相对应的纹理合并为一个个对应图集,如所有模型的主纹理合并一个图集,法线纹理合并为一个纹理。也可以让美术去合并。脚本的话可以使用unity 自带的Texture2D.PackTextures()方法区合并图集。但是还要对网格的UV做处理。