之前做的一个模型详细数据分析的工具,记录一下。
可以针对指定目录进行扫描,找出数据超标的模型。
模型数据规范是自定义的ScriptObject类,可以创建文件很方便的对当前使用的标准规范进行修改。
再逐个对模型进行分析,找到更为精确的数据超标点。
以下是代码:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class ModelAnalyserEditor : EditorWindow
{
#region member variables
//Scanning Model Folder Path
private readonly string modelRootPath = "Assets/";
private string modelPath = "";
private string curScanningPath = "";
//规范数值文件
private ModelStandardData modelStandard;
//规范标准值
private int standardValue_TotalVertex = 6000;
private int standardValue_TotalTriangle = 6000;
private int standardValue_TotalDrawCall = 6;
private int standardValue_TotalTexCount = 4;
private float standardValue_TotalTexSize = 20;
//规范最大值
private int maxValue_TotalVertex = 8000;
private int maxValue_TotalTriangle = 10000;
private int maxValue_TotalDrawCall = 20;
private int maxValue_TotalTexCount = 12;
private float maxValue_TotalTexSize = 60;
//需要分析的模型prefab
private GameObject model;
//mesh:
//total vertCount
private int totalVertexCount = 0;
//total triangleCount
private int totalTriangleCount = 0;
//material:
//total drawcall(预估)
private int totalDrawCall = 0;
//texture:
//tex count
private int totalTexCount = 0;
//total size
private float totalTexSize = 0f;
//RGBA format texture name
private string rgbaFormatTex = "";
//mesh 详细数据List
private List<MeshDetal> meshDetals;
//材质 详细数据List
private List<MaterialDetal> materialDetals;
//贴图 详细数据List
private List<TextureDetal> textureDetals;
//不合规范的模型信息List
private List<string> substandardModels;
//GUI parameter
private Vector2 detalPartScrollPos = Vector2.zero;
private Vector2 scanningPartScrollPos = Vector2.zero;
private bool meshFoldout = false;
private bool materialFoldout = false;
private bool textureFoldout = false;
private GUIStyle sketchyDataStyle;
private GUIStyle detalDataStyle;
#endregion
private ModelAnalyserEditor()
{
titleContent = new GUIContent("Model Analyser");
}
[MenuItem("Tools/Model Analyser")]
private static void CreateWindows()
{
GetWindow<ModelAnalyserEditor>();
}
private string substandardModelStr = "不合规范的模型路径:\n";
private void OnGUI()
{
#region Init GUIStyle
sketchyDataStyle = new GUIStyle(GUI.skin.box)
{
alignment = TextAnchor.UpperLeft,
fontSize = 12,
richText = true
};
sketchyDataStyle.normal.textColor = GUI.skin.button.normal.textColor;
RectOffset rectOffset = new RectOffset(15, 5, 5, 5);
detalDataStyle = new GUIStyle(sketchyDataStyle)
{
fontSize = 11,
margin = rectOffset
};
#endregion
GUILayout.Space(10);
#region Scanning Model
modelStandard = EditorGUILayout.ObjectField("Model standard :", modelStandard, typeof(ModelStandardData), true) as ModelStandardData;
GUILayout.BeginHorizontal();
GUILayout.Label("扫描文件夹路径: " + modelRootPath, GUILayout.Width(145));
modelPath = EditorGUILayout.TextArea(modelPath, GUILayout.MaxWidth(600));
GUILayout.EndHorizontal();
GUILayout.Label("当前扫描路径: " + curScanningPath, GUILayout.MaxWidth(800));
GUILayout.Space(10);
if (GUILayout.Button("Scanning"))
{
InitVariables();
InitStandard();
modelPath = modelRootPath + modelPath.Replace("\\", "/");
curScanningPath = modelPath;
ScanningAllAsset();
modelPath = "";
substandardModelStr = "不合规范的模型路径:\n";
if (substandardModels != null && substandardModels.Count > 0)
{
for (int i = 0; i < substandardModels.Count; i++)
substandardModelStr += "\n" + substandardModels[i];
}
}
scanningPartScrollPos = GUILayout.BeginScrollView(scanningPartScrollPos, false, false);
GUILayout.Box(new GUIContent(substandardModelStr), sketchyDataStyle, new[] { GUILayout.MaxWidth(3000) });
GUILayout.EndScrollView();
#endregion
GUILayout.Space(10);
#region Analyser
model = EditorGUILayout.ObjectField("Model prefab: ", model, typeof(GameObject), true) as GameObject;
if (GUILayout.Button("Analyser"))
{
InitVariables();
if (model == null)
{
Debug.LogError("Model prefab is null!");
return;
}
else
AnalyserModel(model);
}
PrintSketchyData();
detalPartScrollPos = GUILayout.BeginScrollView(detalPartScrollPos, false, false);
PrintDetalData();
GUILayout.EndScrollView();
#endregion
}
#region Print Data
private void PrintSketchyData()
{
if (rgbaFormatTex == "")
rgbaFormatTex = "无";
else if (rgbaFormatTex != "无")
rgbaFormatTex = "<color=#ff0000ff>" + rgbaFormatTex + "</color>";
//Print Sketchy Data
string text = "";
text += "总顶点数: " + CheckValue(totalVertexCount, standardValue_TotalVertex, maxValue_TotalVertex).Key + "\n";
text += "总面数: " + CheckValue(totalTriangleCount, standardValue_TotalTriangle, maxValue_TotalTriangle).Key + "\n";
text += "DrawCall: " + CheckValue(totalDrawCall, standardValue_TotalDrawCall, maxValue_TotalDrawCall).Key + "\n";
text += "贴图数量: " + CheckValue(totalTexCount, standardValue_TotalTexCount, maxValue_TotalTexCount).Key + "\n";
text += "贴图内存: " + CheckValue(totalTexSize, standardValue_TotalTexSize, maxValue_TotalTexSize).Key + " MB\n";
text += "未压缩贴图: " + rgbaFormatTex + "\n";
GUIContent content = new GUIContent(text);
GUILayout.Box(content, sketchyDataStyle, new[] { GUILayout.Height(95), GUILayout.Width(815) });
}
private void PrintDetalData()
{
GUIContent content = new GUIContent();
string text = null;
meshFoldout = EditorGUILayout.Foldout(meshFoldout, "Mesh Detal Data: ");
if (meshFoldout)
{
if (meshDetals != null && meshDetals.Count > 0)
{
for (int i = 0; i < meshDetals.Count; i++)
{
text = "Mesh: " + meshDetals[i].name + "\n";
text += "顶点数:" + CheckValue(meshDetals[i].vertexCount, 2000, 5000).Key + "\n";
text += "面数: " + CheckValue(meshDetals[i].triangleCount, 3000, 6000).Key + "\n";
text += "Submesh: " + CheckValue(meshDetals[i].submeshCount, 2, 5).Key + "\n";
text += "占用内存: " + meshDetals[i].size + " MB" + "\n";
text += "资源路径: " + meshDetals[i].path;
content.text = text;
GUILayout.Box(content, detalDataStyle, new[] { GUILayout.Height(88), GUILayout.Width(800) });
}
}
}
materialFoldout = EditorGUILayout.Foldout(materialFoldout, "Material Detal Data: ");
if (materialFoldout)
{
if (materialDetals != null && materialDetals.Count > 0)
{
for (int i = 0; i < materialDetals.Count; i++)
{
text = "材质: " + materialDetals[i].name + "\n";
text += "shader: " + materialDetals[i].shaderName + "\n";
text += "占用内存: " + materialDetals[i].size + " KB" + "\n";
text += "DrawCall: " + CheckValue(materialDetals[i].drawCall, 2, 4).Key + "\n";
text += "资源路径:" + materialDetals[i].path;
content.text = text;
GUILayout.Box(content, detalDataStyle, new[] { GUILayout.Height(75), GUILayout.Width(800) });
}
}
}
textureFoldout = EditorGUILayout.Foldout(textureFoldout, "Texture Detal Data: ");
if (textureFoldout)
{
if (textureDetals != null && textureDetals.Count > 0)
{
for (int i = 0; i < textureDetals.Count; i++)
{
text = "贴图: " + textureDetals[i].name + "\n";
text += "MaxSize: " + CheckValue(textureDetals[i].maxSize, 512, 1024).Key + "x" + CheckValue(textureDetals[i].maxSize, 512, 1024).Key + "\n";
text += "贴图内存: " + CheckValue(textureDetals[i].size, 4, 8).Key + " MB\n";
text += "贴图格式: " + textureDetals[i].format + "\n";
text += "资源路径: " + textureDetals[i].path + "\n";
content.text = text;
GUILayout.Box(content, detalDataStyle, new[] { GUILayout.Height(75), GUILayout.Width(800) });
}
}
}
}
#endregion
#region AnalyserData
private void AnalyserModel(GameObject model)
{
SkinnedMeshRenderer[] smrs = model.GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (var smr in smrs)
{
if (smr.sharedMesh != null)
AnalyserMesh(smr.sharedMesh);
}
MeshFilter[] meshFilters = model.GetComponentsInChildren<MeshFilter>();
foreach (var meshFilter in meshFilters)
{
if (meshFilter.sharedMesh != null)
AnalyserMesh(meshFilter.sharedMesh);
}
Renderer[] renderers = model.GetComponentsInChildren<Renderer>();
foreach (var renderer in renderers)
{
//特效数据不计入角色里
if(!renderer.GetComponent<ParticleSystem>())
{
bool castShadow = true;
if (renderer.shadowCastingMode == UnityEngine.Rendering.ShadowCastingMode.Off)
castShadow = false;
Material[] mats = renderer.sharedMaterials;
if (mats != null && mats.Length > 0)
{
foreach (var mat in mats)
{
AnalyserMaterial(mat, castShadow);
AnalyserTexture(mat);
}
}
}
}
}
private void AnalyserMesh(Mesh mesh)
{
if (mesh != null)
{
MeshDetal meshDetal = new MeshDetal
{
name = mesh.name,
vertexCount = mesh.vertexCount,
triangleCount = mesh.triangles.Length / 3,
submeshCount = mesh.subMeshCount,
size = GetAssetSize(mesh) / 1024,
path = AssetDatabase.GetAssetPath(mesh)
};
totalVertexCount += meshDetal.vertexCount;
totalTriangleCount += meshDetal.triangleCount;
meshDetals.Add(meshDetal);
}
}
private void AnalyserMaterial(Material mat, bool castShadow)
{
if (mat != null && mat.shader != null)
{
string matPath = AssetDatabase.GetAssetPath(mat);
MaterialDetal materialDetal = new MaterialDetal
{
guid = AssetDatabase.AssetPathToGUID(matPath),
name = mat.name,
shaderName = mat.shader.name,
size = GetAssetSize(mat),
drawCall = GetMaterialDrawCall(mat, castShadow),
path = matPath
};
totalDrawCall += materialDetal.drawCall;
if (materialDetals != null && !materialDetals.Contains(materialDetal))
materialDetals.Add(materialDetal);
}
}
private void AnalyserTexture(Material mat)
{
if (mat == null) return;
SerializedObject serializedMat = new SerializedObject(mat);
SerializedProperty savedProperties = serializedMat.FindProperty("m_SavedProperties");
SerializedProperty texEnvs = savedProperties.FindPropertyRelative("m_TexEnvs");
if (texEnvs == null || texEnvs.arraySize <= 0) return;
for (int i = 0; i < texEnvs.arraySize; i++)
{
SerializedProperty data = texEnvs.GetArrayElementAtIndex(i);
SerializedProperty texProperties = data.FindPropertyRelative("second").FindPropertyRelative("m_Texture");
if (texProperties != null && texProperties.propertyType == SerializedPropertyType.ObjectReference
&& texProperties.objectReferenceValue != null)
{
Texture tex = texProperties.objectReferenceValue as Texture;
if (tex != null)
{
string texPath = AssetDatabase.GetAssetPath(tex);
TextureImporter importer = AssetImporter.GetAtPath(texPath) as TextureImporter;
if (importer == null) continue;
TextureImporterPlatformSettings androidSettings = importer.GetPlatformTextureSettings("Android");
TextureDetal textureDetal = new TextureDetal()
{
guid = AssetDatabase.AssetPathToGUID(texPath),
name = tex.name,
maxSize = importer.maxTextureSize,
size = GetAssetSize(tex) / 1024,
format = androidSettings.format.ToString(),
path = texPath,
};
if (textureDetals != null && !textureDetals.Contains(textureDetal))
{
textureDetals.Add(textureDetal);
totalTexSize += textureDetal.size;
totalTexCount++;
if (androidSettings.format == TextureImporterFormat.RGBA32
|| androidSettings.format == TextureImporterFormat.RGB24)
{
rgbaFormatTex += tex.name + " ";
textureDetal.format = "<color=#ff0000ff>" + textureDetal.format + "</color>";
}
}
}
}
}
}
private void ScanningAllAsset(string searchPattern = "*.prefab")
{
if (substandardModels == null)
substandardModels = new List<string>();
else
substandardModels.Clear();
List<string> paths = new List<string>(Directory.GetFiles(modelPath, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".prefab") || s.EndsWith(".fbx") || s.EndsWith(".FBX")));
foreach (var path in paths)
{
GameObject modelAsset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (modelAsset != null)
{
AnalyserModel(modelAsset);
string str = null;
var vertexKV = CheckValue(totalVertexCount, standardValue_TotalVertex, maxValue_TotalVertex);
var triangleKV = CheckValue(totalTriangleCount, standardValue_TotalTriangle, maxValue_TotalVertex);
var drawCallKV = CheckValue(totalDrawCall, standardValue_TotalDrawCall, maxValue_TotalDrawCall);
var texCountKV = CheckValue(totalTexCount, standardValue_TotalTexCount, maxValue_TotalTexCount);
var texSizeKV = CheckValue(totalTexSize, standardValue_TotalTexSize, maxValue_TotalTexSize);
if (!vertexKV.Value)
str += "\n<color=#ff0000ff>顶点数超标</color>";
if (!triangleKV.Value)
str += "\n<color=#ff0000ff>面数超标</color>";
if (!drawCallKV.Value)
str += "\n<color=#ff0000ff>DrawCall超标</color>";
if (!texCountKV.Value)
str += "\n<color=#ff0000ff>贴图数量超标</color>";
if (!texSizeKV.Value)
str += "\n<color=#ff0000ff>贴图大小超标</color>\n";
if (str != null)
{
str = AssetDatabase.GetAssetPath(modelAsset) + str;
substandardModels.Add(str);
}
}
InitVariables();
}
}
#endregion
#region Tool Methods
/// <summary>
/// 返回asset占用的内存大小(单位:kb)
/// </summary>
/// <param name="o">asset</param>
/// <returns></returns>
private float GetAssetSize(Object o)
{
float size = (float)UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(o) / 1024;
return size;
}
/// <summary>
/// 计算传入的材质可能产生的DrawCall
/// </summary>
/// <param name="mat">material</param>
/// <returns></returns>
private int GetMaterialDrawCall(Material mat, bool castShadow)
{
int drawCall = 0;
for (int i = 0; i < mat.passCount; i++)
{
string passName = mat.GetPassName(i);
string passNameToLower = passName.ToLower();
if (passNameToLower != "meta" && passNameToLower != "deferred"
&& mat.GetShaderPassEnabled(passName))
{
if (!castShadow && passNameToLower == "shadowcaster")
continue;
drawCall++;
}
}
return drawCall;
}
/// <summary>
/// 初始化所有变量和数组
/// </summary>
private void InitVariables()
{
totalVertexCount = 0;
totalTriangleCount = 0;
totalDrawCall = 0;
totalTexCount = 0;
totalTexSize = 0f;
rgbaFormatTex = "";
if (meshDetals == null)
meshDetals = new List<MeshDetal>();
else
meshDetals.Clear();
if (materialDetals == null)
materialDetals = new List<MaterialDetal>();
else
materialDetals.Clear();
if (textureDetals == null)
textureDetals = new List<TextureDetal>();
else
textureDetals.Clear();
}
/// <summary>
/// 初始化一下标准数值
/// </summary>
private void InitStandard()
{
if (modelStandard == null)
modelStandard = CreateInstance<ModelStandardData>();
standardValue_TotalVertex= modelStandard.totalVertex;
standardValue_TotalTriangle = modelStandard.totalTriangle;
standardValue_TotalDrawCall = modelStandard.totalDrawCall;
standardValue_TotalTexCount = modelStandard.totalTexCount;
standardValue_TotalTexSize = modelStandard.totalTexSize;
maxValue_TotalVertex = modelStandard.maxTotalVertex;
maxValue_TotalTriangle = modelStandard.maxTotalTriangle;
maxValue_TotalDrawCall = modelStandard.maxTotalDrawCall;
maxValue_TotalTexCount = modelStandard.maxTotalTexCount;
maxValue_TotalTexSize = modelStandard.maxTotalTexSize;
}
/// <summary>
/// 输入的值转换为带颜色的str,大于最大值为红色,标准值到最大值之间为黄色,低于标准值为白色
/// </summary>
/// <param name="value">输入的值</param>
/// <param name="standardValue">标准值</param>
/// <param name="maxValue">最大值</param>
/// <returns>返回一个键值对,key为处理后的value转str,value为bool值,判断当前值是否超标</returns>
private KeyValuePair<string, bool> CheckValue(float value, float standardValue, float maxValue)
{
KeyValuePair<string, bool> result;
if (value > maxValue)
result = new KeyValuePair<string, bool>(string.Format("<color=#ff0000ff>{0}</color>", value.ToString()), false);
else if (value > standardValue)
result = new KeyValuePair<string, bool>(string.Format("<color=#ffff00ff>{0}</color>", value.ToString()), true);
else
result = new KeyValuePair<string, bool>(value.ToString(), true);
return result;
}
#endregion
#region Tool Class
private struct MeshDetal
{
public string name;
public int vertexCount;
public int triangleCount;
public int submeshCount;
public float size;
public string path;
}
private class MaterialDetal
{
public string guid;
public string name;
public string shaderName;
public float size;
public int drawCall;
public string path;
public override bool Equals(object obj)
{
var detal = obj as MaterialDetal;
return detal != null &&
guid == detal.guid;
}
public override int GetHashCode()
{
return -1324198676 + EqualityComparer<string>.Default.GetHashCode(guid);
}
}
private class TextureDetal
{
public string guid;
public string name;
public int maxSize;
public float size;
public string format;
public string path;
public override bool Equals(object obj)
{
var detal = obj as TextureDetal;
return detal != null &&
guid == detal.guid;
}
public override int GetHashCode()
{
return -1324198676 + EqualityComparer<string>.Default.GetHashCode(guid);
}
}
#endregion
}
[CreateAssetMenu(fileName = "New ModelStandardData Asset", menuName = "Custom Asset/Create ModelStanardData")]
public class ModelStandardData : ScriptableObject
{
[Header("总顶点数")]
public int totalVertex = 6000;
public int maxTotalVertex = 8000;
[Header("总三角面数")]
public int totalTriangle = 6000;
public int maxTotalTriangle = 10000;
[Header("总DrawCall")]
public int totalDrawCall = 6;
public int maxTotalDrawCall = 20;
[Header("总贴图数量")]
public int totalTexCount = 4;
public int maxTotalTexCount = 12;
[Header("总贴图占用内存大小")]
public float totalTexSize = 20;
public float maxTotalTexSize = 60;
}