Yuyeyyy · Graphics

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 布局,可表示的极值上限是 215=327682^{15} = 32768。UV 值从 32769 起就会编码失败。写检查插件时如果按 16384 卡阈值,会漏掉一整段区间——这个坑我踩过一次,插件报告"全部通过",但资产在引擎里就是有问题。

2. welding tolerance 是 0.01 cm,它会导致拆簇。

顶点焊接容差 0.01 cm 这个默认值,对植被这类资产是致命的:BuildLeafClusters 会把单株植物拆成多个簇。后果是风动相位在簇边界处不连续——看起来就是"同一株草,一半在动一半不动"。

所以检查插件里我加了一条:对植被类资产,输出簇数量与分布的统计,簇数异常多就提示检查焊接容差。

输出形式

不要只打日志。日志美术不会看,看了也定位不到资产。输出要满足两点:

  1. 可点击跳转:扫描结果做成列表,双击直接选中并聚焦到内容浏览器里的资产
  2. 可导出:能导出 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

做工具的三条经验

回头看这三件东西,有几条是通用的:

  1. 校验前移,比工具精度更重要。 工具跑在美术提交前,价值远大于跑在 TA 验收时。前者是在减少错误产生,后者只是减少我的工作量。
  2. 输出要能直接定位。 打日志没用,能双击跳到资产才有用。工具的使用者是美术,不是你自己。
  3. 不要试图自动化审美。 工具只负责"有客观标准"的部分。把这部分吃掉之后,人眼看的部分才有价值。

小结

这三件套加起来的代码量都不大,但把验收从"逐个点开看"变成了"扫一遍报告看异常项"。对 TA 来说,这类工具的价值不在于技术难度,在于它把你脑子里的标准固化成了可复现的流程——标准不再因人而异,也不再因为周五下午累了就放水。

评论