【Unity编辑器工具】模型数据分析工具

之前做的一个模型详细数据分析的工具,记录一下。

可以针对指定目录进行扫描,找出数据超标的模型。
模型数据规范是自定义的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;

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于Unity的3D模型编辑器是一种利用游戏引擎开发的工具,用于创建、编辑和操作3D模型的软件。通过这个编辑器用户可以进行模型的创作、修改和优化,实现各种形状、纹理和动画效果。 以下是一些常见的功能和特点,可以在Unity3D模型编辑器中实现: 1. 模型导入和导出:支持常见的3D模型文件格式,如FBX、OBJ等,并能够导入和导出这些文件,与其他软件进行兼容。 2. 建模工具:提供丰富的建模工具,如绘制、拉伸、旋转、缩放等,以便用户可以创建各种形状和结构的模型。 3. 材质编辑:允许用户为模型添加材质和纹理,调整颜色、光照、透明度等属性,以获得更真实的渲染效果。 4. 动画编辑:支持动画制作,用户可以为模型添加骨骼、关键帧和动作序列,并进行动画编辑和调整。 5. 粒子特效:提供创建和编辑粒子特效的工具,让用户可以为模型添加各种粒子效果,如火焰、烟雾、爆炸等。 6. 物理模拟:支持物理引擎,让用户可以为模型添加碰撞体、刚体等物理属性,实现真实的物理效果和交互。 7. UV编辑:提供模型的UV编辑工具,让用户可以调整纹理的映射方式,以实现更精细的纹理贴图效果。 8. 网格优化:提供网格优化工具,可以帮助用户优化模型的顶点数、面数等,以减少模型的资源消耗和渲染负荷。 通过使用Unity3D模型编辑器,开发者可以创建出精美的3D模型,并进行各种编辑和优化操作,以满足项目需求。同时,Unity的强大性能和跨平台支持,也使得这个编辑器成为了许多游戏开发者、设计师和艺术家的首选工具之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值