unity脚本-FBX自动化模型面数校验

根据目前模型资源平均面数预算进行脚本制作,自动化校验模型面数是否符合规范。

*注:文件格式为.cs。需要放置在unity资源文件夹Assets>Editor下。

测试效果(拖一个fbx文件进unity时自动检测):

以下为完整代码

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;

public class FBXFaceBudgetValidator : AssetPostprocessor
{
    // 配置:不同前缀对应的面数预算(不区分大小写)
    private static readonly Dictionary<string, int> FACE_BUDGETS = new Dictionary<string, int>(
        StringComparer.OrdinalIgnoreCase
    )
    {
        {"hair_", 4000},
        {"hat_", 5000},
        {"headdress_", 5000},
        {"caps_", 5000},
        {"clothing_", 6500},
        {"shoe_", 2000},
        {"glasses_", 1000},
        {"mask_", 1500},
        {"glove_", 1000},
        {"necklace_", 1000},
        {"scarf_", 1000},
        {"bracelet_", 2500},
        {"waist_", 1000},
        {"satchel_", 1000},
        {"backpack_", 2500},
        {"wing_", 2500},
        {"cape_", 1000},
        {"earring_", 300},
        {"instrument_",3000}
    };

    // 配置:忽略校验的文件夹路径
    private static readonly string[] IGNORE_PATHS = { "Assets/Models/LowPoly" };

    void OnPostprocessModel(GameObject model)
    {
        try
        {
            string assetPath = assetImporter.assetPath;
            //Debug.Log($"[校验器] 开始处理: {assetPath}");

            if (IsInIgnorePath(assetPath))
            {
                Debug.Log($"[校验器] 已忽略路径: {assetPath}");
                return;
            }

            string fileName = Path.GetFileNameWithoutExtension(assetPath);
            var sortedPrefixes = FACE_BUDGETS.Keys.OrderByDescending(p => p.Length);

            int maxTriangles = -1;
            string matchedPrefix = "";
            foreach (var prefix in sortedPrefixes)
            {
                if (fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                {
                    maxTriangles = FACE_BUDGETS[prefix];
                    matchedPrefix = prefix;
                    break;
                }
            }

            if (maxTriangles == -1)
            {
                string error = $"<color=red>命名错误</color>: {fileName}\n允许的前缀列表:\n{string.Join("\n", FACE_BUDGETS.Keys)}";
                Debug.LogError(error, model);
                return;
            }

            int totalTriangles = CalculateTriangleCount(model);
            Debug.Log($"[校验器] 总面数合规: {totalTriangles}");

            if (totalTriangles > maxTriangles)
            {
                string error = $"<color=red>面数超标</color>: {fileName} ({matchedPrefix})\n预算: {maxTriangles}, 实际: {totalTriangles}";
                Debug.LogError(error, model);
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[校验器] 发生异常: {ex}");
        }
    }

    private bool IsInIgnorePath(string assetPath)
    {
        try
        {
            string fullAssetPath = Path.GetFullPath(assetPath)
                .Replace('\\', '/')
                .TrimEnd('/');

            foreach (var path in IGNORE_PATHS)
            {
                string fullIgnorePath = Path.GetFullPath(path)
                    .Replace('\\', '/')
                    .TrimEnd('/') + "/";

                if (fullAssetPath.StartsWith(fullIgnorePath, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }
        catch
        {
            return false;
        }
    }

    private int CalculateTriangleCount(GameObject model)
    {
        int triangles = 0;
        try
        {
            foreach (var renderer in model.GetComponentsInChildren<Renderer>(true))
            {
                Mesh mesh = null;

                // 处理SkinnedMeshRenderer
                if (renderer is SkinnedMeshRenderer skinnedRenderer)
                {
                    mesh = skinnedRenderer.sharedMesh;
                }
                // 处理普通MeshRenderer
                else if (renderer is MeshRenderer)
                {
                    var filter = renderer.GetComponent<MeshFilter>();
                    if (filter != null)
                    {
                        mesh = filter.sharedMesh;
                    }
                }

                if (mesh != null && mesh.triangles != null)
                {
                    triangles += mesh.triangles.Length / 3;
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[面数计算] 发生异常: {ex}");
        }
        return triangles;
    }
}

内容概要:本文由《未来产业新赛道研究报告》整理而成,涵盖了未来产业在全球范围内的发展态势和竞争形势。报告指出,引领型国家通过全方位体制机制创新,在先进制造、人工智能、量子科技、新一代通信等领域建立了全面领先优势。文中引用了麦肯锡和GVR的数据,预测了人工智能和人形机器人等未来产业的巨大经济潜力。报告还详细介绍了国外和国内对未来产业赛道的重点布局,如量子科技、人工智能、先进网络和通信技术、氢能与储能、生物技术等。此外,报告列举了中国重点省市如北京、上海等的具体发展方向,以及知名研究机构对未来产业热点的分析。最后,报告提出了构建我国未来产业重点赛道目录的建议,包括通用人工智能、高级别自动驾驶、商业航天、人形机器人、新型储能、低空经济、清洁氢、算力芯片、细胞与基因治疗和元宇宙等十大重点赛道。 适用人群:对科技趋势和未来产业发展感兴趣的政策制定者、投资者、企业家和研究人员。 使用场景及目标:①帮助政策制定者了解全球未来产业发展动态,为政策制定提供参考;②为企业提供未来产业布局的方向和重点领域;③为投资者提供投资决策依据,识别未来的投资机会;④为研究人员提供未来科技发展趋势的全景图。 其他说明:报告强调了未来产业在全球经济中的重要性,指出了中国在未来产业布局中的战略定位和发展路径。同时,报告呼吁加强国家顶层设计和行业系统谋划,探索建立未来产业技术预见机制,深化央地联动,推动未来产业高质量发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值