Mesh合批
把很多静止的模型,标记为Batching Static,原本需要把模型一个个送到GPU渲染,Unity会把相同材质相同纹理相同Shader的模型合批成一个大的模型,送到GPU进行渲染,这样就减少DrawCall。可以调用Mesh.CombineMeshes方法,创建CombineInstance[]数组,进行合批。
Static Batching
要求:必须使用相同的材质material,相同的纹理Texture,然后在编辑器里设置为static batching的。
特点:静态批是无法运动的。
所以一般制作流程上,对于场景这些静态的物体都采用静态批,美术会根据场景的规模,将相邻的一片物件的贴图合并到一张或几张1024或512的大图上,这样这些物件可以使用同一个material,就可以静态批在一起,大幅节省DrawCall。 静态批的时候Unity3d会在运行时生成一个合并的大模型,并且为这个模型指定一张共同的贴图,所以这个批在一起的数量是有限的,如果批在一起的定点数过多,它就会自动分成两个批,一个静态批的上限为65535,这是因为Unity默认使用16位Mesh Index Buffer。
详情请阅读 :https://docs.unity3d.com/ScriptReference/Rendering.IndexFormat.html
Dynamic batching
Dynamic batching的原理也很简单,在进行场景绘制之前将所有的共享同一材质的模型的顶点信息变换到世界空间中,然后通过一次Draw call绘制多个模型,达到合批的目的。模型顶点变换的操作是由CPU完成的,所以这会带来一些CPU的性能消耗。并且计算的模型顶点数量不宜太多,否则CPU串行计算耗费的时间太长会造成场景渲染卡顿,所以Dynamic batching只能处理一些小模型。
要求如下:
- 目前Unity限制能进行Dynamic batching的模型最高能有900个顶点属性。这里注意不是900个顶点,而是900个顶点属性。如果我们在Shader中使用了Vertex Position,Normal and single UV,那么能够进行Dynamic batching的模型最多只能够有300个顶点。如果我们在Shader中使用了Vertex Position、Normal、UV0、UV1 and Tangent那么顶点的数量就减少到180个。
- 批在一起的所有的模型应用同样的缩放值
- 使用相同的材质
- 相同的一张lightmap
- 不能使用多pass的shader
- 不能接收阴影
DrawCall
DrawCall是CPU通过底层图像编程接口发出的渲染命令,GPU读取渲染命令执行渲染操作。
过多的DrawCall影响绘制的原因:
主要是每次绘制时,CPU通过底层图像编程接口发出渲染命令DrawCall,而每个DrawCall需要很多准备工作,检测渲染状态、提交渲染数据、提交渲染状态,而GPU本身可以很快处理完渲染任务。DrawCall过多,CPU负载过多,而GPU性能闲置。
渲染状态
渲染状态定义场景中的网格是怎样被渲染出来的。例如使用哪个顶点着色器、哪个片元着色器、光源属性、材质等。如果没有更改渲染状态,所有的网格将使用同一种渲染状态。
Mesh.CombineMeshes方法
- 遍历当前对象和子对象的MeshFilter组件,创建CombineInstance[]数组。
- 获取filter.sharedMesh赋值给combine[i].mesh。
- obj.transform.worldToLocalMatrix * filter.transform.localToWorldMatrix得到当前对象和子对象在本地空间的矩阵,赋值给combine[i].transform。
- 遍历子对象所有的MeshRenderer组件,获取render.sharedMaterial存储在一个List中,然后赋值给当前对象的MeshRenderer的sharedMaterials。
- 调用mesh.CombineMeshes(combine),合并Mesh。
- 同时,还可以调用Unwrapping.GenerateSecondaryUVSet(meshFilter.sharedMesh);合并第二套UV。
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class CombineMesh
{
private const string MESH_PATH = "Assets/GameData/";
[MenuItem("Examples/CombineMesh")]
static void Combine()
{
GameObject obj = Selection.activeGameObject;
if (obj.GetComponent<MeshRenderer>() == null)
{
obj.AddComponent<MeshRenderer>();
}
if (obj.GetComponent<MeshFilter>() == null)
{
obj.AddComponent<MeshFilter>();
}
List<Material> material = new List<Material>();
Matrix4x4 matrix = obj.transform.worldToLocalMatrix;
MeshFilter[] filters = obj.GetComponentsInChildren<MeshFilter>();
int filterLength = filters.Length;
CombineInstance[] combine = new CombineInstance[filterLength];
for (int i = 0; i < filterLength; i++)
{
MeshFilter filter = filters[i];
MeshRenderer render = filter.GetComponent<MeshRenderer>();
if (render == null)
{
continue;
}
if (render.sharedMaterial != null && !material.Contains(render.sharedMaterial))
{
material.Add(render.sharedMaterial);
}
combine[i].mesh = filter.sharedMesh;
//对坐标系施加变换的方法是 当前对象和子对象在世界空间中的矩阵 左乘 当前对象从世界空间转换为本地空间的变换矩阵
//得到当前对象和子对象在本地空间的矩阵。
combine[i].transform = matrix * filter.transform.localToWorldMatrix;
render.enabled = false;
}
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
Mesh mesh = new Mesh();
mesh.name = "Combine";
//合并Mesh
mesh.CombineMeshes(combine);
meshFilter.sharedMesh = mesh;
//合并第二套UV
Unwrapping.GenerateSecondaryUVSet(meshFilter.sharedMesh);
MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
renderer.sharedMaterials = material.ToArray();
renderer.enabled = true;
MeshCollider collider = new MeshCollider();
if (collider != null)
{
collider.sharedMesh = mesh;
}
string tempPath = MESH_PATH + obj.name + "_mesh.asset";
AssetDatabase.CreateAsset(meshFilter.sharedMesh, tempPath);
PrefabUtility.DisconnectPrefabInstance(obj);
}
}
对坐标系施加变换的方法
矩阵变换,当前对象和子对象在世界空间中的矩阵 左乘 当前对象从世界空间转换为本地空间的变换矩阵,得到当前对象和子对象在本地空间的矩阵。