ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity Editor 材质贴图自动化装配工具:从零搭建与核心实现

Unity Editor 材质贴图自动化装配工具:从零搭建与核心实现 1. 项目缘起与核心需求拆解做过Unity项目的人大概都有过这种体验美术同学丢过来一个文件夹里面躺着几十上百个模型每个模型配了三四张贴图——Albedo、Normal、Metallic、Roughness、AO命名规则还五花八门。有的叫xxx_basecolor.png有的叫xxx_Albedo.tga还有的干脆就是xxx_01.png、xxx_02.png。然后你就要在Project窗口里一个个点开模型展开材质球把贴图一张张拖到对应的槽位上。一个下午过去眼睛花了手也酸了还容易拖错——把法线贴到Albedo槽里这种事我自己就干过不止一次。这个Unity Editor 材质贴图自动化装配工具要解决的就是这个痛点。它的核心逻辑很朴素根据贴图文件的命名规则自动识别每张贴图的类型然后批量装配到对应材质的正确槽位上。听起来简单但真要做好里面涉及的东西比想象中多——命名规则的容错、贴图类型的判定、材质球的创建与复用、Shader的匹配、Undo操作的兼容、AssetDatabase的刷新时机每一个环节都有坑。这个工具适合谁用三类人最需要一是技术美术TA日常要处理大量美术资源导入二是独立开发者没有专门的TA资源管理全靠自己三是任何需要批量处理Unity资源的程序员。哪怕你只是偶尔接个外包手里有一堆模型要整理这个工具也能帮你省下大把时间。我自己的项目里一个场景大概有80到120个道具模型每个模型平均4张贴图。手动装配一次大概要花2到3个小时而且中途一旦发现某张贴图命名不对还得回头重新找。用了自动化工具之后整个流程压缩到几十秒剩下的时间可以拿去调光照和做优化。这篇文章就把我从零搭建这个工具的完整思路、关键代码和踩过的坑全部摊开来讲。2. 整体设计与技术选型思路2.1 为什么选择Editor脚本而不是运行时方案这个工具的本质是资源导入管线的一部分它操作的是Project窗口里的资产文件而不是场景里的运行时对象。所以它必须跑在Editor环境下用UnityEditor命名空间下的API。运行时方案比如在游戏启动时动态加载贴图并赋值解决的是另一个问题——那是给玩家看的不是给开发者用的。选择Editor脚本还有几个实际好处。第一可以直接调用AssetDatabase系列API对资产进行增删改查这是运行时做不到的。第二可以挂到AssetPostprocessor上实现导入即自动装配美术同学把文件拖进Project窗口的那一刻材质就已经配好了连按钮都不用点。第三Editor脚本的调试成本低改完代码Unity自动编译不像运行时方案还要考虑打包和平台差异。2.2 核心流程的四个阶段整个工具的流程可以拆成四个阶段我用一个表格把每个阶段的输入、输出和关键操作列出来阶段输入关键操作输出扫描识别选中的文件夹或模型遍历目录收集贴图与模型文件文件清单类型判定贴图文件名按关键词匹配贴图类型类型标签材质装配模型贴图类型标签创建/复用材质赋值到槽位配置好的材质收尾刷新修改后的资产保存、刷新、注册Undo可用的资产状态这个流程看起来线性但实际实现时类型判定和材质装配这两步是耦合最紧、坑最多的。类型判定错了后面全错材质装配时如果Shader的槽位名和贴图类型对不上贴图就赋不进去。2.3 命名规则的设计哲学工具能不能用八成取决于命名规则的设计。我的原则是宽进严出。所谓宽进是指识别时要尽量包容各种命名习惯_BaseColor、_Albedo、_Diffuse、_MainTex都应该能识别为Albedo类贴图所谓严出是指一旦识别出来赋值时必须精确匹配Shader的属性名不能有歧义。我见过一些工具要求美术严格按某种命名规范来结果美术不买账工具就废了。反过来如果识别太宽松把_NormalMap和_NormalScale搞混那更麻烦。所以关键词表要精心设计既要覆盖常见写法又要避免误判。具体的匹配策略我在下一章展开。3. 核心细节解析与实操要点3.1 贴图类型判定的关键词策略贴图类型判定是整个工具的地基。我的做法是维护一张关键词映射表每个贴图类型对应一组关键词匹配时按优先级从高到低扫描文件名。为什么要有优先级因为有些关键词会互相包含比如_Normal和_NormalMap如果不设优先级短的那个可能先匹配上导致误判。下面是我实际使用的关键词表节选覆盖了PBR流程里最常见的几类贴图贴图类型关键词按优先级对应Shader属性Albedobasecolor, albedo, diffuse, maintex, base_color_BaseMap / _MainTexNormalnormalmap, normal, norm, nrm_BumpMap / _NormalMapMetallicmetallic, metalness, metal_MetallicMapRoughnessroughness, rough, rgh_RoughnessMapSmoothnesssmoothness, smooth, gloss_SmoothnessMapAOambientocclusion, occlusion, ao_OcclusionMapHeightheightmap, height, displacement, disp_HeightMapEmissionemission, emissive, emit_EmissionMap匹配的时候有个细节要注意大小写不敏感但分隔符要处理。文件名可能是Rock_BaseColor也可能是rock-basecolor还可能是RockBaseColor。我的做法是先把文件名统一转成小写然后把-、空格、.都替换成_再用Contains去匹配关键词。这样rock-basecolor就变成了rock_basecolor能正确匹配到basecolor。注意关键词匹配一定要用Contains而不是StartsWith或EndsWith。因为贴图文件名通常是模型名_贴图类型的格式类型关键词在中间或末尾用StartsWith会漏掉大量文件。还有一个容易忽略的点排除干扰词。比如_NormalScale这种参数贴图如果文件名里带了normal会被误判为法线贴图。我的处理方式是维护一个黑名单包含scale、intensity、strength、mask这类词匹配到黑名单词的文件直接跳过。这个黑名单是我踩了好几次坑之后才加上的一开始没加结果把一张Rock_NormalScale.png当法线贴图赋进去了模型表面直接变成一片紫色。3.2 材质球的创建与复用逻辑贴图识别出来之后下一步是决定用哪个材质球。这里有两种策略一是每个模型创建一个新材质二是复用已有的材质。两种策略各有适用场景。如果模型是独立资产比如一个道具、一个场景摆件那每个模型创建独立材质更合适因为后续可能要单独调参数。如果模型是同一套资源的变体比如同一棵树的三种颜色那复用材质更省内存但要注意材质实例化的问题。我的工具默认采用**“先查找后创建”**的策略先看模型上是否已经挂了材质如果有就在原材质上改如果没有就新建一个材质命名规则是模型名_Mat。这样既不会重复创建材质也不会覆盖美术手动调过的参数。创建材质的时候Shader的选择很关键。Unity内置的Standard Shader和URP的Lit Shader属性名是不一样的。Standard用的是_Metallic和_GlossinessURP Lit用的是_Metallic和_Smoothness。如果Shader选错了贴图赋进去也显示不出来。我的做法是优先使用项目里已有的材质所用的Shader如果找不到再回退到Shader.Find(Standard)。这样能最大程度保证兼容性。// 材质创建与复用的核心逻辑 private Material GetOrCreateMaterial(GameObject model, string materialName) { var renderer model.GetComponentRenderer(); if (renderer ! null renderer.sharedMaterial ! null) { return renderer.sharedMaterial; } // 尝试从项目里找一个已有的同Shader材质作为模板 Shader targetShader Shader.Find(Standard); var guids AssetDatabase.FindAssets(t:Material); foreach (var guid in guids) { var path AssetDatabase.GUIDToAssetPath(guid); var mat AssetDatabase.LoadAssetAtPathMaterial(path); if (mat ! null mat.shader.name.Contains(Lit)) { targetShader mat.shader; break; } } var newMat new Material(targetShader); newMat.name materialName; return newMat; }这段代码里有个细节renderer.sharedMaterial和renderer.material的区别。sharedMaterial是共享材质修改它会影响所有使用该材质的对象material是实例化材质修改它只影响当前对象。在Editor工具里我们通常希望修改的是资产文件所以应该用sharedMaterial然后通过AssetDatabase保存。3.3 Shader属性名的动态适配不同Shader的属性名差异是装配环节最大的坑。Standard Shader的Albedo属性叫_MainTexURP Lit叫_BaseMapStandard的法线叫_BumpMapURP Lit叫_NormalMap。如果硬编码属性名换个渲染管线工具就废了。我的解决方案是运行时探测拿到Shader之后遍历它的所有属性根据属性名和贴图类型做模糊匹配。具体做法是调用Shader.GetPropertyCount()和Shader.GetPropertyName()拿到所有属性名然后看哪个属性名包含我们期望的关键词。// 动态查找Shader上匹配的属性名 private string FindPropertyName(Shader shader, string[] keywords) { int count shader.GetPropertyCount(); for (int i 0; i count; i) { string propName shader.GetPropertyName(i); string lower propName.ToLower(); foreach (var kw in keywords) { if (lower.Contains(kw.ToLower())) { return propName; } } } return null; }比如Albedo贴图我传入new[] { basemap, maintex, basecolor }函数会返回Shader上实际存在的那个属性名。这样无论项目用的是Standard、URP还是HDRP工具都能自适应。这个设计是我在同时维护两个不同渲染管线的项目时逼出来的一开始硬编码结果每次切管线都要改代码烦不胜烦。提示Shader.GetPropertyName返回的是属性名但赋值时要用Material.SetTexture参数是属性名对应的属性ID。用Shader.PropertyToID转换一下性能更好不过在Editor工具里这点性能差异可以忽略。3.4 Undo系统与资产保存的配合Editor工具如果不支持Undo用户误操作之后没法撤销体验会很差。Unity的Undo系统通过Undo.RegisterCreatedObjectUndo、Undo.RecordObject等API来注册可撤销的操作。但这里有个坑材质是资产文件不是场景对象对材质的修改要用Undo.RecordObject注册而且修改完之后要调用EditorUtility.SetDirty标记脏数据再配合AssetDatabase.SaveAssets保存。我一开始只调了AssetDatabase.SaveAssets没注册Undo结果用户按CtrlZ的时候材质纹丝不动但场景里的对象位置变了整个状态就乱了。后来补上Undo注册问题才解决。顺序也很重要先RecordObject再修改最后SetDirty和SaveAssets。顺序错了Undo可能记录不到修改。// 正确的Undo注册与保存流程 Undo.RecordObject(material, Auto Assign Textures); material.SetTexture(propName, texture); EditorUtility.SetDirty(material); AssetDatabase.SaveAssets();另外AssetDatabase.SaveAssets和AssetDatabase.Refresh的区别也要搞清楚。SaveAssets是把内存里的修改写回磁盘Refresh是重新扫描磁盘上的资产变化。装配完贴图之后两个都要调否则Project窗口里可能看不到更新。4. 完整实操流程与关键环节实现4.1 环境准备与脚本放置这个工具是一个纯Editor脚本不需要任何第三方依赖。新建一个C#脚本放在项目里任意一个Editor文件夹下即可。如果没有Editor文件夹自己建一个因为只有放在Editor文件夹里的脚本才会被Unity识别为Editor脚本不会被打进最终包体。脚本的命名空间建议用UnityEditor相关的类名我习惯叫MaterialAutoAssembler。脚本开头要引入这几个命名空间using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; using System.Linq;System.Linq是为了方便做集合操作比如筛选、排序。System.IO是用来遍历文件目录的。这几个都是标配缺一不可。4.2 菜单入口与批量选择工具的使用入口我设计成两种一种是菜单栏入口通过[MenuItem]特性注册适合手动触发另一种是AssetPostprocessor入口适合自动触发。先讲菜单入口。[MenuItem(Tools/Material Auto Assembler/Assemble Selected)] public static void AssembleSelected() { var selectedObjects Selection.objects; if (selectedObjects null || selectedObjects.Length 0) { EditorUtility.DisplayDialog(提示, 请先在Project窗口选中至少一个模型或文件夹, 知道了); return; } // 收集所有需要处理的模型路径 var modelPaths CollectModelPaths(selectedObjects); if (modelPaths.Count 0) { EditorUtility.DisplayDialog(提示, 选中的对象里没有找到模型文件, 知道了); return; } // 开始批量装配 int successCount 0; try { for (int i 0; i modelPaths.Count; i) { var path modelPaths[i]; EditorUtility.DisplayProgressBar(材质自动装配, $正在处理: {Path.GetFileName(path)}, (float)i / modelPaths.Count); if (AssembleModel(path)) { successCount; } } } finally { EditorUtility.ClearProgressBar(); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorUtility.DisplayDialog(完成, $共处理 {modelPaths.Count} 个模型成功装配 {successCount} 个, 好的); }这里有几个实操细节值得说。第一进度条是必须的因为批量处理可能耗时几十秒没有进度条用户会以为卡死了。第二try-finally保证即使中途出错进度条也能被清理掉不会残留在界面上。第三处理完之后统一SaveAssets和Refresh而不是每个模型都调一次这样性能好很多。4.3 模型路径收集与贴图配对CollectModelPaths函数负责从选中的对象里提取出所有模型文件的路径。如果选中的是文件夹就递归遍历如果选中的是模型文件直接加入列表。这里要注意过滤掉非模型文件比如.meta、.mat这些。private static Liststring CollectModelPaths(Object[] selectedObjects) { var result new Liststring(); var validExtensions new HashSetstring { .fbx, .obj, .blend, .dae }; foreach (var obj in selectedObjects) { string path AssetDatabase.GetAssetPath(obj); if (string.IsNullOrEmpty(path)) continue; if (AssetDatabase.IsValidFolder(path)) { // 递归遍历文件夹 var guids AssetDatabase.FindAssets(t:Model, new[] { path }); foreach (var guid in guids) { var modelPath AssetDatabase.GUIDToAssetPath(guid); if (validExtensions.Contains(Path.GetExtension(modelPath).ToLower())) { result.Add(modelPath); } } } else if (validExtensions.Contains(Path.GetExtension(path).ToLower())) { result.Add(path); } } return result.Distinct().ToList(); }贴图配对的核心逻辑是在模型文件所在目录以及子目录里找同名的贴图文件。比如模型叫Rock_01.fbx那贴图应该叫Rock_01_BaseColor.png、Rock_01_Normal.png之类。匹配的时候用模型名做前缀去掉扩展名然后看贴图文件名是否以这个前缀开头。private static Dictionarystring, string FindTexturesForModel(string modelPath) { string modelName Path.GetFileNameWithoutExtension(modelPath); string dir Path.GetDirectoryName(modelPath); var result new Dictionarystring, string(); // 在模型所在目录及子目录里找贴图 var textureGuids AssetDatabase.FindAssets(t:Texture2D, new[] { dir }); foreach (var guid in textureGuids) { string texPath AssetDatabase.GUIDToAssetPath(guid); string texName Path.GetFileNameWithoutExtension(texPath); // 贴图名必须以模型名开头 if (!texName.StartsWith(modelName, System.StringComparison.OrdinalIgnoreCase)) continue; // 判定贴图类型 string texType DetermineTextureType(texName); if (texType ! null !result.ContainsKey(texType)) { result[texType] texPath; } } return result; }这里有个性能优化点AssetDatabase.FindAssets如果对整个项目搜索会很慢所以一定要限定搜索目录为模型所在目录。另外StartsWith匹配时用OrdinalIgnoreCase避免大小写问题。4.4 贴图类型判定的完整实现DetermineTextureType函数是关键词匹配的核心。我把它写成先转小写、再替换分隔符、然后按优先级匹配、最后检查黑名单的流程。private static readonly Dictionarystring, string[] TypeKeywords new Dictionarystring, string[] { { Albedo, new[] { basecolor, albedo, diffuse, maintex, base_color } }, { Normal, new[] { normalmap, normal, norm, nrm } }, { Metallic, new[] { metallic, metalness, metal } }, { Roughness, new[] { roughness, rough, rgh } }, { Smoothness, new[] { smoothness, smooth, gloss } }, { AO, new[] { ambientocclusion, occlusion, ao } }, { Height, new[] { heightmap, height, displacement, disp } }, { Emission, new[] { emission, emissive, emit } }, }; private static readonly string[] Blacklist { scale, intensity, strength, mask, color }; private static string DetermineTextureType(string fileName) { string lower fileName.ToLower() .Replace(-, _) .Replace( , _) .Replace(., _); // 先检查黑名单 foreach (var bad in Blacklist) { if (lower.Contains(bad)) return null; } // 按优先级匹配 foreach (var kvp in TypeKeywords) { foreach (var kw in kvp.Value) { if (lower.Contains(kw)) { return kvp.Key; } } } return null; }黑名单里我加了color因为_BaseColor和_Color容易和Albedo混淆。但实际上basecolor本身就在Albedo的关键词里所以黑名单的color要小心——它会把basecolor也拦掉。我的处理是黑名单检查放在关键词匹配之前但黑名单里的词要足够具体。color这个词太宽泛了后来我把它改成了vertexcolor和colorid避免误伤。注意关键词匹配的顺序很重要。normalmap要放在normal前面否则normalmap会先匹配到normal虽然结果一样但如果以后有normalmap和normal分属不同类型的情况就会出错。字典的遍历顺序在C#里不保证所以更稳妥的做法是用ListKeyValuePair显式排序。4.5 材质装配的完整流程装配环节把前面几步的成果串起来拿到模型、拿到贴图字典、拿到材质、逐个赋值。private static bool AssembleModel(string modelPath) { var model AssetDatabase.LoadAssetAtPathGameObject(modelPath); if (model null) return false; var textures FindTexturesForModel(modelPath); if (textures.Count 0) return false; var renderer model.GetComponentInChildrenRenderer(); if (renderer null) return false; var material GetOrCreateMaterial(model, Path.GetFileNameWithoutExtension(modelPath) _Mat); if (material null) return false; bool modified false; foreach (var kvp in textures) { string texType kvp.Key; string texPath kvp.Value; var texture AssetDatabase.LoadAssetAtPathTexture2D(texPath); if (texture null) continue; string propName FindPropertyName(material.shader, GetKeywordsForType(texType)); if (string.IsNullOrEmpty(propName)) continue; Undo.RecordObject(material, Auto Assign Textures); material.SetTexture(propName, texture); EditorUtility.SetDirty(material); modified true; } if (modified) { // 把材质挂到模型上 renderer.sharedMaterial material; EditorUtility.SetDirty(renderer); } return modified; }这里有个容易忽略的点renderer.sharedMaterial material这行代码如果模型是FBX导入的直接改sharedMaterial可能不会持久化因为FBX的材质映射是存在.meta文件里的。正确的做法是用ModelImporter的AddRemap方法把材质重映射到我们创建的材质上。这个坑我踩过改完之后在Editor里看着是对的重启Unity就还原了。// 正确的FBX材质重映射 var importer AssetImporter.GetAtPath(modelPath) as ModelImporter; if (importer ! null) { var remap new ModelImporterMaterialRemap(); // 设置重映射关系 importer.AddRemap(new AssetImporter.SourceAssetIdentifier(typeof(Material), 原材质名), material); importer.SaveAndReimport(); }SaveAndReimport会触发重新导入比较耗时所以批量处理时要谨慎使用。如果模型不多可以接受如果模型上百个建议先收集所有重映射关系最后统一reimport。4.6 AssetPostprocessor自动触发方案如果希望美术把贴图拖进Project窗口就自动装配可以挂一个AssetPostprocessor。核心是重写OnPostprocessAllAssets方法在资产导入完成后触发装配。public class MaterialAutoAssemblerPostprocessor : AssetPostprocessor { static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { var modelPaths importedAssets .Where(p p.EndsWith(.fbx) || p.EndsWith(.obj)) .ToList(); if (modelPaths.Count 0) return; foreach (var path in modelPaths) { AssembleModel(path); } AssetDatabase.SaveAssets(); } }这个方案的好处是零操作美术完全无感。坏处是每次导入都会触发如果美术只是微调了一下模型也会重新装配一遍可能覆盖手动调整。所以我的建议是默认关闭自动触发在菜单里加一个开关需要的时候再打开。5. 常见问题与排查技巧实录5.1 贴图赋不进去的排查思路贴图赋不进去原因通常有三类属性名不对、贴图类型没识别出来、材质没保存。排查的时候按这个顺序来。先看属性名。在代码里加一行Debug.Log($Shader: {material.shader.name}, Prop: {propName})看输出的属性名是不是Shader上真实存在的。如果propName是null说明FindPropertyName没找到匹配项这时候要检查Shader的属性列表看是不是关键词写错了。再看贴图类型。在DetermineTextureType里加日志看每张贴图被判定成了什么类型。如果返回null说明关键词没匹配上或者被黑名单拦了。这时候把文件名打印出来手动跑一遍匹配逻辑看卡在哪一步。最后看材质保存。如果Editor里看着赋上了重启Unity就没了那多半是没调AssetDatabase.SaveAssets或者FBX的材质重映射没做。前者加保存调用后者用ModelImporter.AddRemap。5.2 常见问题速查表我把实际使用中遇到的问题整理成一张表方便快速定位现象可能原因解决方法贴图赋进去但显示紫色贴图类型判定错误法线贴图被当成了Albedo检查关键词表确认法线贴图匹配到Normal类型材质球重复创建每次装配都新建材质没有复用检查GetOrCreateMaterial的查找逻辑重启Unity后材质还原FBX材质重映射没做用ModelImporter.AddRemap注册重映射Undo无效没注册Undo或顺序错误先RecordObject再修改最后SetDirty批量处理卡死每个模型都调SaveAndReimport收集所有重映射最后统一reimport贴图匹配到别的模型前缀匹配太宽松用模型名_做前缀加下划线分隔进度条不消失异常导致ClearProgressBar没执行用try-finally包裹5.3 几个我踩过的坑第一个坑法线贴图的颜色空间。法线贴图必须设置为Normal map类型否则光照会出错。Unity在导入贴图时如果文件名带normal会自动识别为Normal map但如果文件名是nrm结尾就不会自动识别。所以工具里要加一步识别为Normal类型的贴图自动把TextureImporter.textureType设为TextureImporterType.NormalMap。这一步不做模型表面会看起来怪怪的。var importer AssetImporter.GetAtPath(texPath) as TextureImporter; if (importer ! null texType Normal importer.textureType ! TextureImporterType.NormalMap) { importer.textureType TextureImporterType.NormalMap; importer.SaveAndReimport(); }第二个坑Metallic和Smoothness的通道打包。很多PBR工作流会把Metallic和Smoothness打包到同一张贴图的R通道和A通道里。这种情况下贴图类型判定会出问题——一张图同时包含两种信息。我的处理是如果检测到metallicsmoothness或metalsmooth这类组合词就把它同时赋给Metallic和Smoothness两个槽位让Shader自己去取对应的通道。第三个坑贴图尺寸不一致。有时候Albedo是2048Normal是1024这本身没问题但如果工具在装配时做了尺寸校验可能会误报。我的建议是不做尺寸校验因为尺寸不一致是美术的合理选择工具不应该干涉。第四个坑多材质模型。一个FBX里可能有多个子网格每个子网格对应一个材质。我的工具默认只处理第一个材质如果模型有多个材质需要遍历renderer.sharedMaterials数组。这个逻辑我后来补上了但要注意材质和贴图的对应关系——多材质模型的贴图命名通常会带材质索引比如Rock_01_Mat1_BaseColor.png匹配时要考虑这个前缀。5.4 性能优化的几个实操技巧批量处理上百个模型时性能是个问题。我总结了几个优化点。第一减少AssetDatabase.FindAssets的调用次数。这个API每次调用都会扫描磁盘很慢。我的做法是一次性扫描整个目录把结果缓存起来后续匹配都在内存里做。第二延迟SaveAndReimport。前面提过SaveAndReimport会触发重新导入非常耗时。批量处理时先收集所有需要reimport的资产最后统一调一次。第三用AssetDatabase.StartAssetEditing和StopAssetEditing包裹批量操作。这两个API会暂停Unity的资产导入队列等所有操作做完再统一导入能显著提升批量处理速度。try { AssetDatabase.StartAssetEditing(); // 批量操作 foreach (var path in modelPaths) { AssembleModel(path); } } finally { AssetDatabase.StopAssetEditing(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); }注意StartAssetEditing和StopAssetEditing必须成对出现且要用try-finally包裹否则一旦中间抛异常Unity的资产导入队列会一直处于暂停状态Project窗口不刷新很麻烦。6. 工具的扩展方向与个人体会这个工具目前覆盖了PBR流程里最常见的几类贴图但实际项目里还有更多细分需求。比如植被贴图带Alpha通道的叶片、Decal贴图、Lightmap这些都可以通过扩展关键词表来支持。我自己的做法是把关键词表做成可配置的ScriptableObject放在Project里需要加新类型时直接改配置不用改代码。另一个扩展方向是反向操作从材质球反推贴图命名是否规范。有时候美术给的资源是反过来的——材质已经配好了但贴图命名乱七八糟。这时候可以写一个检查工具扫描所有材质输出命名不规范的贴图列表让美术去改。这个功能我还没做但思路是现成的。最后分享一个我在实际使用中的体会工具的价值不在于功能多全而在于能不能融入现有的工作流。我一开始做的版本功能很全支持各种命名规则、各种Shader但美术同学用了一次就不用了因为要手动点菜单。后来加了AssetPostprocessor自动触发使用率一下就上去了。所以做工具的时候多想想用户会在什么场景下用它怎么让它无感地嵌入流程比堆功能重要得多。还有一个细节工具的日志输出要克制。我早期版本在每个环节都打日志结果批量处理时Console窗口刷了几百条真正有用的信息被淹没了。后来改成只在出错时打日志正常流程静默只在最后弹一个汇总对话框。这样用户既知道结果又不会被日志干扰。
返回列表