UE5 / Tooling
把目视验收变成自动化:UE5 美术自检工具链三件套
在 UE5 项目里做 TA,很快会发现一件事:真正吃掉时间的不是写 shader,是验收。美术提交一批资产,我得一个个点开看——贴图亮度合不合理、Nanite 开没开、UV 有没有越界。标准全在脑子里,看久了会飘,而且同样的错误每周重复出现。
后来索性把重复的那部分做成工具。这篇记一下落地的三件套,以及做工具时的取舍。
为什么要做工具:验收的三类问题
先把问题分清楚,工具才有针对性:
| 类型 | 例子 | 特点 | 适合的解法 |
|---|---|---|---|
| 数值可判定的 | 贴图平均灰度、粗糙度范围、三角形数 | 有明确阈值 | 脚本自动扫 |
| 配置可判定的 | Nanite 是否开启、UV 通道数、材质槽数 | 读资产元数据即可 | 引擎内插件批量查 |
| 必须人眼看的 | 美术风格、造型比例 | 无客观标准 | 人看,但把前两类筛掉后工作量骤减 |
前两类占了我验收时间的七八成——这才是自动化的目标。不要试图用工具替代审美判断,要让它把"不用审美就能判定的部分"全部吃掉。
一、灰度检测工具(已打包 exe)
解决的痛点
美术交上来的贴图,常见的数值问题就这么几种:
- Albedo 太亮或太暗:PBR 下 albedo 的合理亮度区间是有经验值的,整体偏亮会让物体在场景里"发飘"
- ** roughness 走极端**:全 0(镜面)或全 1(完全粗糙),说明没认真调,或者导出时通道填错了
- 金属度非二值:金属度在 0.3–0.7 这种中间值通常是错误的,物理上材质要么金属要么非金属
- 通道填错:ORM/ORN 打包时把信息放错通道,肉眼在编辑器里看缩略图很难发现
检测项
| 检测项 | 判定逻辑 | 输出 |
|---|---|---|
| 平均灰度 | 转线性后求加权平均亮度,落在经验区间外告警 | 数值 + 判定 |
| 直方图分布 | 看是否过度集中在两端(对比度异常) | 分布 + 判定 |
| 通道独立性 | 逐通道统计,判断某通道是否为常量(填错) | 每通道 mean/std |
| 极端像素占比 | 纯黑/纯白像素比例,超过阈值提示 clipping | 百分比 |
关于"打包成 exe"
这个工具最后打包成了 exe 交给美术自己跑,这是刻意的选择:
- 美术的机器上不一定有 Python 环境,给脚本等于没给
- 工具跑在提交前,而不是等 TA 验收时才发现问题——把校验前移比工具本身更重要
- 双击就能用,输出一份报告,美术自己先看一遍,能过滤掉一大半低级错误
报告以文件形式落到资产旁边,附在提交里。我验收时只需要看异常项。
二、引擎内 Nanite 自检插件
为什么必须在引擎内做
Nanite 相关的检查,脱离引擎基本查不了。原因很简单:Nanite 的很多状态是构建时生成的,外部工具读不到。而且检查项里有一批必须读资产元数据——UV 通道数、顶点色、材质槽——这些在引擎里就是几次 API 调用。
检查项
| 检查项 | 为什么查 | 典型症状 |
|---|---|---|
| Nanite 是否开启 | 忘了开,或者不该开的开了 | 远处 LOD 跳变 / 性能没收益 |
| 三角形数 | 低模开 Nanite 是负收益 | 集群数太少,开销大于收益 |
| UV 通道数与越界 | Nanite 的 UV 编码有上限 | 编码失败或 UV 精度崩掉 |
| 顶点色通道占用 | 顶点色是风动等效果的载体 | 通道被别的用途占满 |
| 材质槽数 | 槽数影响光栅化开销 | 单 mesh 槽数过多 |
| 是否含 Nanite 不友好的材质特性 | 半透明、WPO 这类特性与 Nanite 的组合有限制 | 渲染结果与预期不符 |
| Bounds 是否正确 | 裁剪包围盒不对会导致误剔除 | 镜头一动植被就闪 |
两个必须记牢的 Nanite 事实
这两条是我实际踩过的,也是排查 Nanite 问题时最先要确认的:
1. UV 编码上限是 32768,不是 16384。
Nanite 的 UV codec 是 uint24 布局,可表示的极值上限是 。UV 值从 32769 起就会编码失败。写检查插件时如果按 16384 卡阈值,会漏掉一整段区间——这个坑我踩过一次,插件报告"全部通过",但资产在引擎里就是有问题。
2. welding tolerance 是 0.01 cm,它会导致拆簇。
顶点焊接容差 0.01 cm 这个默认值,对植被这类资产是致命的:BuildLeafClusters 会把单株植物拆成多个簇。后果是风动相位在簇边界处不连续——看起来就是"同一株草,一半在动一半不动"。
所以检查插件里我加了一条:对植被类资产,输出簇数量与分布的统计,簇数异常多就提示检查焊接容差。
输出形式
不要只打日志。日志美术不会看,看了也定位不到资产。输出要满足两点:
- 可点击跳转:扫描结果做成列表,双击直接选中并聚焦到内容浏览器里的资产
- 可导出:能导出 CSV/表格,方便挂在验收流程里当附件
做不到这两点,工具大概率会沦为我自己的玩具。
三、虹膜生成器 MF_IrisGenerator + SphereMask
前两个工具是"查错",这个是"生成"——把重复的手工劳动变成参数。
原来的做法
角色眼睛的虹膜靠手绘贴图。问题:
- 每个角色画一张,风格不统一
- 想调虹膜半径、纹理密度、瞳孔大小,得回 Photoshop 重画
- 换一个角色就要重来一遍
程序化方案
做成材质函数 MF_IrisGenerator,核心是 SphereMask + 极坐标:
// MF_IrisGenerator 核心思路(Custom 节点)
// 输入:UV(0-1)、IrisRadius、PupilScale、FiberDensity、LimbalRingWidth
float2 p = UV * 2.0 - 1.0; // 转到 -1..1,中心为原点
float r = length(p); // 到中心的归一化距离
float a = atan2(p.y, p.x); // 极角
// 1) 瞳孔:SphereMask 做中心硬遮罩
float pupil = 1.0 - SphereMask(p, IrisRadius * PupilScale);
// 2) 虹膜主体:环形区域
float iris = SphereMask(p, IrisRadius) - SphereMask(p, IrisRadius * PupilScale);
// 3) 放射状纤维:极角方向的高频噪声
float fiber = Noise2D(float2(a * FiberDensity, r * 4.0));
// 4) 外圈暗环(limbal ring):靠边缘 r 的 smoothstep
float ring = smoothstep(IrisRadius * 0.92, IrisRadius, r);
// 合成
float3 col = lerp(PupilColor, IrisColor, pupil);
col *= lerp(1.0, fiber, iris * 0.6);
col = lerp(col, RingColor, ring * LimbalRingWidth);
SphereMask 在这里的价值是一步拿到带软边的圆形/环形遮罩,比手写 smoothstep(length(p), ...) 直观,也不容易在边缘处出现硬锯齿。
收益
| 项目 | 手绘贴图 | 程序化 |
|---|---|---|
| 单个角色制作 | 数十分钟 | 调参数,几分钟 |
| 风格一致性 | 取决于画的人 | 同一个函数,天然一致 |
| 修改成本 | 重画 | 拖参数 |
| 贴图内存 | 每角色一份 | 共享,几乎为 0 |
做工具的三条经验
回头看这三件东西,有几条是通用的:
- 校验前移,比工具精度更重要。 工具跑在美术提交前,价值远大于跑在 TA 验收时。前者是在减少错误产生,后者只是减少我的工作量。
- 输出要能直接定位。 打日志没用,能双击跳到资产才有用。工具的使用者是美术,不是你自己。
- 不要试图自动化审美。 工具只负责"有客观标准"的部分。把这部分吃掉之后,人眼看的部分才有价值。
小结
这三件套加起来的代码量都不大,但把验收从"逐个点开看"变成了"扫一遍报告看异常项"。对 TA 来说,这类工具的价值不在于技术难度,在于它把你脑子里的标准固化成了可复现的流程——标准不再因人而异,也不再因为周五下午累了就放水。