我的某个游戏运行之后,看了下draw call,发现上千个draw call了,非常大的数值,不过我在手机上测试了一下,竟然没有明显的卡顿,哈哈哈,很强,不过还是要优化一下的,所以先想办法降低draw call了,我看了一个,是游戏的地图产生了大量的dc,我这个游戏是由四个地图组成的,每个地图都由几百个小物体组成,所以四个地图应该是由两千多个物体组成的,刚开始我想着要不合并模型的网格试试吧,然后发现出问题了,一些显示一些隐藏了,可能是太多物体了,合并容易出问题吧,所以我就打算启用Shader中的Enable GPU Instancing,启用后,会自动进行静态批处理,所以dc就会大幅度的减少。而且我发现我的游戏物体的材质Shader还没有Enable GPU Instancing,想着自己写个有Enable GPU Instancing的Shader吧,但是我又看了一下,Unity中的Mobile/Diffuse的Shader就有这个选项,然后就用这个Shader了吧,那么问题又来了,两千多个物体,难道要我自己一个一个的改Shader并且启用GPU Instancing吗?当然这样也行,前提是你很闲,无聊到没事做的时候可以这样做。所以我的办法是自己写个编辑器脚本来批量修改Shader并启用GPU Instancing。
编辑器脚本如下:
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEditor;
using UnityEngine;
public class ReplaceShaderByFileDir : EditorWindow
{
Shader shader;
Shader originShader;
bool isShowReplaceGo = false; //是否显示被替换的物体
string tipMsg = null;
MessageType tipMsgType = MessageType.Info;
List replaceGoList = new List();
int matCount = 0; //材质的数量
Vector2 scrollPos = Vector2.zero;
[MenuItem("Editor/替换场景中的shader")]
public static void OpenWindow()
{
//创建窗口
ReplaceShaderByFileDir window = GetWindow(false, "替换场景中的shader");
window.Show();
}
void OnGUI()
{
GUILayout.Label("原shader:");
originShader = (Shader)EditorGUILayout.ObjectField(originShader, typeof(Shader), true);
//ObjectField(string label, Object obj, Type objType, bool allowSceneObjects, GUILayoutOption[] paramsOptions)
//label字段前面的可选标签 obj字段显示的物体 objType物体的类型 allowSceneObjects允许指定场景物体..
//返回:Object,用户设置的物体
GUILayout.Label("替换shader :");
shader = (Shader)EditorGUILayout.ObjectField(shader, typeof(Shader), true);
GUILayout.Space(8);
//开始一个水平组,所有被渲染的控件,在这个组里一个接着一个被水平放置。该组必须调用EndHorizontal关闭。
GUILayout.BeginHorizontal();
if (GUILayout.Button("批量替换", GUILayout.Height(30)))
{
Replace();
}
if (GUILayout.Button("重置", GUILayout.Height(30)))
{
Reset();
}
//关闭水平组
GUILayout.EndHorizontal();
//提示信息
if (!string.IsNullOrEmpty(tipMsg))
{
//创建一个帮助框,第一个参数是显示的文本,第二个参数是帮助框的提示图标类型
EditorGUILayout.HelpBox(tipMsg, tipMsgType);
}
//创建勾选框
isShowReplaceGo = GUILayout.Toggle(isShowReplaceGo, "显示被替换的GameObject");
if (isShowReplaceGo)
{
if (replaceGoList.Count > 0)
{
//开始滚动视图,scrollPos用于显示的滚动位置
scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Width(Screen.width), GUILayout.Height(Screen.height - 200));
foreach (var go in replaceGoList)
{
EditorGUILayout.ObjectField(go, typeof(GameObject), true);
}
//结束滚动视图
GUILayout.EndScrollView();
}
else
{
EditorGUILayout.LabelField("替换个数为0");
}
}
}
///
/// 替换Shader
///
void Replace()
{
replaceGoList.Clear();
if (shader == null)
{
tipMsg = "shader为空!";
tipMsgType = MessageType.Error;
return;
}
if (originShader == null)
{
tipMsg = "指定的shader为空!";
tipMsgType = MessageType.Error;
return;
}
else if (originShader.Equals(shader))
{
tipMsg = "替换的shader和指定的shader相同!";
tipMsgType = MessageType.Error;
return;
}
Dictionary matDict = GetAllScenceMaterial();
List replaceMatList = new List();
foreach (var item in matDict)
{
GameObject tempGo = item.Key;
Material[] mats = item.Value;
int length = mats.Length;
for (int i = 0; i < length; i++)
{
var mat = mats[i];
if (mat != null && mat.shader.Equals(originShader))
{
if (!mat.shader.Equals(shader))
{
replaceGoList.Add(tempGo);
if (!replaceMatList.Contains(mat))
replaceMatList.Add(mat);
}
}
}
}
//替换Material的数量
int replaceMatCount = replaceMatList.Count;
for (int i = 0; i < replaceMatCount; i++)
{
UpdateProgress(i, replaceMatCount, "替换中...");
//替换Shader
replaceMatList[i].shader = shader;
//启用GPU Instancing
replaceMatList[i].enableInstancing = true;
//设置脏标志,标记目标物体已改变,当资源已改变并需要保存到磁盘,Unity内部使用dirty标识来查找
EditorUtility.SetDirty(replaceMatList[i]);
}
// 刷新编辑器,使刚创建的资源立刻被导入,才能接下来立刻使用上该资源
AssetDatabase.Refresh();
// 一般所有资源修改完后调用,调用后Unity会重新导入修改过后的资源
AssetDatabase.SaveAssets();
tipMsg = "替换成功!替换了" + replaceMatCount + "个Material," + replaceGoList.Count + "个GameObject";
tipMsgType = MessageType.Info;
//关闭进度条
EditorUtility.ClearProgressBar();
}
///
/// 替换shader的可视化进程
///
void UpdateProgress(int progress, int progressMax, string info)
{
string title = "Processing...[" + progress + " / " + progressMax + "]";
float value = (float)progress / progressMax;
//显示进度条
EditorUtility.DisplayProgressBar(title, info, value);
}
///
/// 重置
///
void Reset()
{
tipMsg = null;
shader = null;
originShader = null;
matCount = 0;
replaceGoList.Clear();
isShowReplaceGo = false;
}
///
/// 获取所有场景中的Material
///
///
Dictionary GetAllScenceMaterial()
{
Dictionary dict = new Dictionary();
List gos = GetAllSceneGameObject();
foreach (var go in gos)
{
Renderer render = go.GetComponent();
if (render != null)
{
Material[] mats = render.sharedMaterials;
if (mats != null && mats.Length > 0 && !dict.ContainsKey(go))
{
dict.Add(go, mats);
matCount += mats.Length;
}
}
}
return dict;
}
///
/// 获取所有场景中的物体
///
///
List GetAllSceneGameObject()
{
List list = new List();
//获取当前活动的场景
Scene scene = SceneManager.GetActiveScene();
//获取场景中所有根游戏对象
GameObject[] rootGos = scene.GetRootGameObjects();
foreach (var go in rootGos)
{
Transform[] childs = go.transform.GetComponentsInChildren(true);
foreach (var child in childs)
{
list.Add(child.gameObject);
}
}
return list;
}
}
在编写编辑器时,如果需要修改Unity序列化资源(如Prefab,美术资源,ScriptableObject等类型),修改后应将该资源标记为已更改:
EditorUtility.SetDirty(Object target)
但标记为已更改的资源Unity不会立即保存到磁盘,这时需要调用
AssetDataBase.SaveAssets(),一般所有资源修改完后调用,调用后Unity会重新导入修改过后的资源(数量大费时间)。