texLab:在 UE5 编辑器内完成 PBR 贴图打包,预览即最终输出

AI3D 发表于 4 周前 浏览 0 分类 创作分享

简介

我是 Pavel Kuzmenko,一名技术美术和工具开发者,在动画长片与游戏开发方面拥有经验。多年来,我使用过 Maya、Unreal Engine 和 Substance 3D Designer 等工具,主要从事制作的技术方面 — 构建工具、改进工作流,以及处理那些常常出现在 DCC 与生产管线下游所用工具之间的问题。

这些工作中的很大一部分,都是在移除管线中不必要的步骤,并让工具的行为更加可预测。texLab 的想法也正是从这里开始的。

我为什么要构建它

时间 是制作中最宝贵的资源。技术美术在 Unreal Engine 之外重新打包纹理上浪费了多少时间?通常的工作流是导出各个贴图,打开另一个 DCC,决定通道布局,打包所有内容,重新导入结果,然后在 Unreal Engine 中再次检查。到那时,磁盘上的纹理可能与你批准的预览并不完全一致。

然后你对下一个文件夹重复同样的事情。这种往复流程正是我想通过 texLab 消除的,texLab 是一个适用于 Unreal Engine 5.7 和 5.8 的原生 C++ 插件,可以直接在编辑器内将 PBR 贴图打包成可用于生产的布局。它还可以将现有的打包纹理解包回单独的贴图。

大多数纹理工具往往属于两类之一:DCC 功能或资源包。texLab 两者都不是。我希望它介于两者之间:一个管线工具,将基于 DCC 的纹理打包中一些有用的部分带入 Unreal,而不把 Unreal 变成另一个 DCC。

自动化是这一决定的另一部分。如果工具无法确定一个打包纹理是 ORM 还是 RMA,我宁愿它停下来并报告这种模糊性,而不是悄悄做出错误假设。打包布局本身只是项目的一部分。同样重要的是保持美术师所看到的、工具所验证的,以及最终写入磁盘的内容之间的联系。

这最终就是我希望 texLab 解决的问题。这个项目有趣的部分不只是打包本身。而是它周围发生的事:保持源数据完整、预览实际压缩效果、在写入任何内容之前验证批量操作,并确保从预览一直到导出都使用同一条处理路径。本文会梳理这些工程决策,以及在把纹理打包原型转变为面向生产的编辑器工具时出现的问题。

texLab:

问题所在

引入隐蔽生产 bug 最简单的方式之一,就是把通道顺序弄错。ORM、RMA、ARM 和 MSR 的文件名看起来很相似,但包含的数据却完全不同。基于文件名的启发式判断看似方便,但把 Metallic 放进 Roughness 通道,可能要等到流程后期才会被发现。这就是 texLab 使用显式语义、而不是靠文件名猜测的原因。

语义规则定义了每个源贴图或打包布局实际包含的内容。例如:

R = AO,G = Roughness,B = Metallic

如果 texLab 遇到未知的打包后缀,它会报告这一歧义,而不是去猜测它的含义。语义数据库基于 JSON,因此流程 TD 可以在不修改插件本身的情况下扩展或调整规则。

统一的打包图

手动、预览和批处理都使用同一套打包实现。每个工作流都没有各自独立的管线。
打包零散贴图,以及解包和重新打包已有的打包纹理,都走同一条底层处理管线。Preview、Create Outputs 和 Batch Execute 也使用同一个执行器。

我刻意避免使用单独的组装路径,因为预览和导出需要产生相同的结果。如果它们走不同的代码路径,最终一定会出现分歧。你预览到的,就是最终写入的内容。

texLab:

预览也是输出的一部分

如果最终资产在 BC 压缩后会看起来不同,那么一个理想化、未压缩图像的预览在生产中就没多大用处。因此 texLab 为同一处理结果提供了多种视图:

  • Working — 浮点处理结果
  • Encoded — 经过 BC7/BC5 压缩往返后的结果
  • Delta — Working 与 Encoded 之间的差异
  • Split Channels — 每个通道的实际内容

Encoded 预览遵循与输出相同的通用编码路径。工作图像会被转换为相应的 8 位格式,填充至 4×4 块,经由虚幻引擎的 ITextureCompressorModule 处理,并按照所选配方进行压缩。

支持的压缩路径包括:

  • BC7
  • BC5
  • AutoDXT

压缩后的结果随后会被解码回浮点数以进行显示。
这并非旨在逐位复现最终烘焙的平台资源。Unreal 的烘焙器以及针对特定平台的处理可能会引入额外的差异。相反,它为美术师提供了一个更有用的参考:他们实际要签字确认的那种压缩损失。

texLab:

使用浮点精度工作

texLab 在整个处理流程中始终以浮点数保存图像数据。它不会过早地将所有内容转换为 FColor,而是使用 FImage/FImageView 进行处理,并将数据保持为浮点数,直到导出时才进行转换。只有在实际写出输出时,才会转换为 8 位并进行块压缩。标量运算在线性空间中执行,并且始终遵循相同的顺序:

  • Gloss → Roughness(需要时)
  • 反相
  • 输入色阶
  • 中间调调整
  • 对比度
  • 输出色阶
texLab:

例如,对比度围绕 0.5 应用:

t = saturate((t − 0.5) × Contrast + 0.5)

随后,最终结果会被映射到所请求的输出范围中。

法线重建采用相同的方法。DirectX 打包的 XY 值首先从 [0,1] 转换为有符号空间,而 Z 则从半球中重建出来:

  • X,Y = 2 × packed − 1
  • Z = sqrt(max(0, 1 − X² − Y²))

对于 OpenGL 源,Y 会在重建之前先被翻转。Manual、Preview 和 Export 都经由这同一套有序的处理流程栈,因此能在整个工作流中保持结果一致。

texLab:

安全 vs 紧凑

texLab 将其打包策略分为两大类:Safe(安全)和 Compact(紧凑)。

安全策略会尽可能保持各通道独立。例如:

  • ORM → BC7
  • Normal → BC5

Compact 策略将更多信息打包进更少的纹理资源中,有助于减少纹理采样和流送开销。

其中较为激进的布局之一是:

GRAH 和 NMG

Compact 并不是一个隐藏的“快速”或低质量预设。它是在纹理资源、采样和压缩之间的一种权衡。UI 让这种权衡变得明确,而 Encoded 预览则让美术师在创建最终资产之前评估由此产生的压缩效果。

随附的 Gold Quick Start 从 ORM + N [Safe] 开始。这让首个工作流聚焦于核心打包流程,而不会立即引入更激进的策略。

批量处理,不做盲目写入

对于单张纹理,工作流程很直观:

源 → 通道分配 → 预览 → 创建输出

对于文件夹,流程则变为:

扫描 → 重建分组 → 验证 → 执行

Validate 是一次真正的试运行。它会检查:

  • 语义覆盖
  • 名称
  • 分组
  • 预估节省量
  • 计划输出的文件

它不会向磁盘写入任何内容。

在运行 Execute 之前,内容浏览器保持不变。

默认的输出策略是 Create New,而不是覆盖现有资产。虚幻的撤销系统无法可靠地恢复二进制纹理数据负载,因此静默替换源资产并不是我认为适合生产工具的安全默认行为。

生成的纹理会将来源信息存储在 Asset User Data 中,包括源贴图、打包策略、任务信息和备注。

这意味着即使几个月后,你仍然可以追溯打包纹理的来源以及它是如何创建的。

texLab:

无缝 烘焙

Seamless Bake 在打包之前直接将平铺接缝清理应用于源像素,因此无需再把纹理送到另一个外部应用程序中处理。

该操作会以原生分辨率处理所提供的图像,并使用这些处理后的像素来构建最终的打包结果。

其基本方法是将 50% 的图像偏移与覆盖遮罩(cover mask)以及可选的轻量级变形相结合。概念上:

  • offset = wrap(x + floor(W / 2), y + floor(H / 2))
  • out = lerp(offset, original, cover)

默认的遮罩(mask)和精度(precision)参数控制接缝混合的强度。

还有一个可选的、考虑环绕(wrap-aware)的高通操作,用于光照均衡。由于额外的处理开销,它默认处于禁用状态。

手动(Manual)和批处理(Batch)使用相同的烘焙路径。预览(Preview)显示烘焙后的像素,而执行(Execute)会写入这些相同的像素。

解包(Unpack)不会尝试反转无缝烘焙(Seamless Bake)。一旦结果被烘焙进纹理中,原始像素就已消失。没有任何元数据可以简单地被移除以恢复它们。

texLab:

源数据保真度

texLab 尽可能基于原始导入的源数据进行处理。

必要时,它会回退到 FTextureSource,而且这种回退是显式说明的,而不是静默发生。

我不希望该工具使用任何碰巧可用的显示 mip 或压缩表示形式作为进一步处理的源数据。

重新打包已压缩的数据可能引入额外误差,尤其是在法线重建或光泽度转粗糙度等操作中。

源数据与对其执行的操作同样重要。

texLab:
texLab:

架构

该插件分为三个模块:

texLab:

依赖方向是:

编辑器 → 核心

核心模块从不依赖编辑器。这意味着,举例来说,分组和语义匹配可以在不启动 Slate 或编辑器其余 UI 的情况下进行测试。

配置按以下层级组织:

随附资源 → 项目配置 → 用户保存

JSON 资源同样使用 schemaVersion,因此随着格式的演进,预设和语义定义可以被迁移。

该实现完全基于原生 C++ 与 Slate。编辑器默认值通过 UDeveloperSettings / INI 处理,并支持项目级覆盖。

该插件本身仍然仅限编辑器使用,不包含任何玩法或运行时功能。

texLab:

让编辑器保持响应

批处理会带来另一个在简单演示中并不明显的问题。

一个包含数十张大纹理的文件夹,不应在 texLab 处理它们时让 Unreal 慢如蜗牛。

texLab 使用 CPU 优先的处理模型。图像处理可以在游戏线程之外运行,而 UObject 变更、资产创建和保存则保持在合适的 Unreal 线程边界内。

批处理工作也按块处理,并且在工作图像尺寸适当的情况下设置上限,以控制内存使用。

这使内存使用保持可预测,而不是一次性将整个大纹理文件夹加载到内存中。

texLab:

你能获得什么

texLab 覆盖多种工作流程,具体取决于你需要做什么。

手动模式

一次打包一个材质:

  • 源文件
  • 打包图表
  • 实时预览
  • 通道调整
  • 创建输出
  • 材质 / 材质实例设置
  • 添加到现有材质
  • 解包

批处理

使用以下功能处理整个文件夹:

  • 扫描
  • 重建组
  • 建议
  • 验证
  • 分块执行
  • 解包

Library

Library 中包含随插件附带的 JSON 策略、纹理指引、激活状态,以及这些策略在项目层级的副本。

Material Builder

已经打包好的纹理可以直接连接到虚幻材质模板,无需再次经过打包流程。

Virtual Textures

当输出超过配置的尺寸阈值时,可以启用虚拟纹理支持。

Help

编辑器内也直接提供了快速入门。

完整文档可在此处查看:

texLab 文档

快速入门

通过以下方式启用插件:

Edit → Plugins

然后打开:

Tools → texLab

首次运行时,选择以下路径下的演示纹理:

/texLab/texLabDemo/Textures/Magma

选择:

ORM + N [Safe]

打开 Encoded 预览并创建输出。

创建完成后,选中生成的纹理并查看:

Details → Asset User Data

第一个工作流程应该耗时不到五分钟,无需单独建立项目,也无需切换到外部纹理软件。

结语 

最初只是一个相对直接的纹理打包工具,最终却涉及远比通道布局更广泛的一系列问题。

最大的挑战之一,是确保预览管线与执行 Create Outputs 时实际写入 .uasset 的内容保持一致——而不是维护一个可能逐渐偏离导出路径的独立模拟预览。另一个挑战是让 Manual 和 Batch 共用同一套统一的打包图表,同时在构建新的打包时优先使用原始导入源而非已压缩的纹理数据,以避免不必要的画质损失。

许多较小的制作层面的问题,只有在用真实资产使用该工具后才变得明显:ORM/RMA/ARM 语义含糊不清、批量运行期间的内存占用与编辑器响应速度、输出资产的来源追踪,以及让文件夹级别的操作能够安全执行。错误的通道顺序可能会一直是一个静默的 bug,直到它波及数十个材质;这也是 texLab 采用明确语义规则而非根据文件名猜测的重要原因。

其中一些问题在我刚开始时并不明显。它们逐渐塑造了整体架构:用于组装和重新打包的单一执行器、在 Execute 之前进行 dry-run 的 Validate、写入时的来源记录,以及在结果提交之前展示 BC7/BC5 往返转换的 Encoded 预览。

最终,texLab 与其说是在打包纹理,不如说是在围绕这一操作构建一条可预测、可视化的管线。这大概也是开发过程中最有趣的部分。

链接

Pavel Kuzmenko,技术美术

相关链接

原文链接:https://80.lv/articles/texlab-packing-pbr-maps-inside-ue5-without-lying-about-the-preview

喜欢这篇内容吗?