根据目前模型资源平均面数预算进行脚本制作,自动化校验模型面数是否符合规范。
*注:文件格式为.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;
}
}