logo80lv
Articlesclick_arrow
Talentsclick_arrow
Events
Workshops
Aboutclick_arrow
profile_login
Log in
0
Save
Copy Link
Share

texLab: Packing PBR Maps Inside UE5 Without Lying About the Preview

Pavel Kuzmenko spoke about the native C++ plugin texLab for Unreal Engine, detailing the tool's core design, its JSON-based semantic database that pipeline TDs can extend, and explaining some of the additional workflows.

Introduction

I'm Pavel Kuzmenko, a Technical Artist and tools developer with experience in feature animation and game development. Over the years, I've worked with tools such as Maya, Unreal Engine, and Substance 3D Designer, among others, mostly on the technical side of production — building tools, improving workflows, and dealing with the problems that tend to appear between DCCs and the tools used further down the production pipeline.

A lot of that work has been about removing unnecessary steps from the pipeline and making tools behave more predictably. That's also where the idea for texLab started.

Why I Built It

Time is the most valuable resource in production. How much time do technical artists waste repacking textures outside of Unreal Engine? The usual workflow is to export the individual maps, open another DCC, decide on a channel layout, pack everything, reimport the result, and then check it again in Unreal Engine. At that point, the texture on disk may not quite match the preview you approved.

Then you do the same thing for the next folder. That round-trip is what I wanted to get rid of with texLab, which is a native C++ plugin for Unreal Engine 5.7 and 5.8 that packs PBR maps into production-ready layouts directly inside the editor. It can also unpack existing packed textures back into individual maps.

Most texture tools tend to fall into one of two categories: DCC features or asset packs. texLab is neither. I wanted it to sit somewhere in between: a pipeline tool that brings some of the useful parts of DCC-based texture packing into Unreal without turning Unreal into another DCC.

Automation was another part of that decision. If the tool can't determine whether a packed texture is ORM or RMA, I'd rather have it stop and report the ambiguity than quietly make the wrong assumption. The packing layouts themselves are only part of the project. What matters just as much is keeping the connection between what the artist sees, what the tool validates, and what eventually gets written to disk.

That’s ultimately what I wanted texLab to solve. The interesting part of the project isn't just the packing itself. It's what happens around it: keeping the source data intact, previewing the actual compression, validating batch operations before writing anything, and making sure the same processing path is used from preview all the way to export. This article goes through those engineering decisions and the problems that came up while turning a texture-packing prototype into a production-oriented Editor tool.

The Problem

One of the easiest ways to introduce a silent production bug is to get the channel order wrong. ORM, RMA, ARM, and MSR can have similar-looking filenames while containing completely different data. A filename heuristic might seem convenient, but putting Metallic into Roughness can go unnoticed until much later in the pipeline. That's why texLab uses explicit semantics instead of filename guessing.

Semantic rules define what each source map or packed layout actually contains. For example:

R = AO, G = Roughness, B = Metallic

If texLab encounters an unknown packed suffix, it reports the ambiguity instead of trying to guess what it means. The semantic database is JSON-based, so pipeline TDs can extend or adjust the rules without modifying the plugin itself.

One Pack Graph

Manual, Preview, and Batch all use the same packing implementation. There are no separate pipelines for each workflow.
Packing loose maps, as well as unpacking and repacking existing packed textures, goes through the same underlying processing pipeline. Preview, Create Outputs, and Batch Execute use the same executor too.

I deliberately avoided a separate assembly path because Preview and Export need to produce the same result. If they follow different code paths, they will eventually disagree. What you preview is what gets written.

Preview Is Part of the Output

A preview of an idealized, uncompressed image isn't very useful in production if the final asset will look different after BC compression. texLab therefore provides several views of the same processing result:

  • Working — the floating-point processing result
  • Encoded — the result after the BC7/BC5 compression round-trip
  • Delta — the difference between Working and Encoded
  • Split Channels — the actual contents of each channel

The Encoded preview follows the same general encoding path used for the output. The working image is converted to the appropriate 8-bit format, padded to 4×4 blocks, passed through Unreal Engine's ITextureCompressorModule, and compressed according to the selected recipe.

The supported compression paths include:

  • BC7
  • BC5
  • AutoDXT

The compressed result is then decoded back into a float for display.
This isn't meant to reproduce the final cooked platform asset bit-for-bit. Unreal's cooker and platform-specific processing can introduce additional differences. Instead, it gives the artist a much more useful reference: the kind of compression loss they are actually signing off on.

Working in Floating Point

texLab keeps image data in floating point throughout the processing pipeline. Instead of converting everything to FColor early on, it works with FImage/FImageView and keeps the data in floating point until export. Conversion to 8-bit and block compression only happens when the output is actually written. Scalar operations are performed in linear space and always follow the same order:

  • Gloss → Roughness, when required
  • Invert
  • Input levels
  • Midtone adjustment
  • Contrast
  • Output levels

For example, contrast is applied around 0.5:

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

The final result is then mapped into the requested output range.

Normal reconstruction follows the same approach. DirectX packed XY values are first converted from [0,1] into signed space, and Z is reconstructed from the hemisphere:

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

For OpenGL sources, Y is flipped before reconstruction. Manual, Preview, and Export all run through this same ordered processing stack, which keeps the result consistent throughout the workflow.

Safe vs Compact

texLab separates its packing strategies into two broad categories: Safe and Compact.

Safe strategies keep channels independent where possible. For example:

  • ORM → BC7
  • Normal → BC5

Compact strategies pack more information into fewer texture resources, helping reduce texture samples and streaming overhead.

One of the more aggressive layouts is:

GRAH and NMG

Compact isn't meant to be a hidden "Fast" or lower-quality preset. It's a trade-off between texture resources, sampling, and compression. The UI makes that trade-off explicit, while the Encoded preview lets the artist evaluate the resulting compression before creating the final asset.

The shipped Gold Quick Start starts with ORM + N [Safe]. This keeps the first workflow focused on the core packing process without immediately introducing the more aggressive strategies.

Batch Without Blind Writes

For a single texture, the workflow is straightforward:

Sources → Channel Assignment → Preview → Create Outputs

For folders, the process becomes:

Scan → Rebuild Groups → Validate → Execute

Validate is a real dry run. It checks:

  • semantic coverage
  • names
  • groups
  • estimated savings
  • planned output files

It doesn't write anything to disk.

The Content Browser stays unchanged until Execute is run.

The default output policy is Create New rather than overwriting existing assets. Unreal's undo system doesn't reliably restore binary texture payloads, so silently replacing source assets isn't something I consider a safe default for a production tool.

Generated textures also store provenance in Asset User Data, including the source maps, packing strategy, job information, and notes.

This means you can still trace a packed texture back to its sources and how it was created, even months later.

Seamless Bake

Seamless Bake applies tiling cleanup directly to the source pixels before packing, so there's no need to send the texture through another external application.

The operation works on the provided images at their native resolution and uses those processed pixels to build the final pack.

The basic approach combines a 50% image offset with a cover mask and optional lightweight warping. Conceptually:

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

The default mask and precision parameters control how aggressively the seam is blended.

There's also an optional wrap-aware high-pass operation for lighting equalization. It's disabled by default because of the additional processing cost.

Manual and Batch use the same bake path. Preview shows the baked pixels, and Execute writes those same pixels.

Unpack doesn't try to reverse Seamless Bake. Once the result has been baked into the texture, the original pixels are gone. There's no metadata that can simply be removed to restore them.

Source Fidelity

texLab tries to work from the original imported source data whenever possible.

When necessary, it falls back to FTextureSource, and that fallback is made explicit rather than happening silently.

I didn't want the tool to use whatever display mip or compressed representation happened to be available as the source for further processing.

Repacking already compressed data can introduce additional errors, especially for operations like Normal reconstruction or gloss-to-roughness conversion.

The source data matters just as much as the operation being performed on it.

Architecture

The plugin is split into three modules:

The dependency direction is:

Editor → Core

Core never depends on the Editor. This means, for example, that grouping and semantic matching can be tested without starting Slate or the rest of the editor UI.

Configuration is layered as:

Shipped Resources → Project Config → User Saved

JSON resources also use a schemaVersion, so presets and semantic definitions can be migrated as the format evolves.

The implementation is entirely native C++ with Slate. Editor defaults are handled through UDeveloperSettings / INI, with project-level overrides.

The plugin itself remains Editor-only and contains no gameplay or runtime functionality.

Keeping the Editor Responsive

Batch processing creates another problem that isn't obvious in a simple demo.

A folder with dozens of large textures shouldn't bring Unreal to a crawl while texLab processes them.

texLab uses a CPU-first processing model. Image processing can run off the game thread, while UObject mutation, asset creation, and saving stay within the appropriate Unreal thread boundaries.

Batch work is also processed in chunks, and working image sizes are capped where appropriate to keep memory usage under control.

This keeps memory usage predictable instead of loading an entire folder of large textures into memory at once.

What You Get

texLab covers several workflows, depending on what you need to do.

Manual

Pack a single material at a time:

  • Sources
  • Pack Graph
  • Live Preview
  • Channel adjustments
  • Create Outputs
  • Material / Material Instance setup
  • Add to Existing Material
  • Unpack

Batch

Process entire folders with:

  • Scan
  • Rebuild Groups
  • Suggestions
  • Validate
  • Chunked Execute
  • Unpack

Library

The Library contains the shipped JSON strategies, texture guidance, activation, and project-level copies of those strategies.

Material Builder

Already-packed textures can be connected to Unreal material templates without running them through the packing process again.

Virtual Textures

Virtual texture support can be enabled when the output exceeds the configured size threshold.

Help

A Quick Start is also available directly inside the editor.

The full documentation is available here:

texLab Documentation

Quick Start

Enable the plugin through:

Edit → Plugins

Then open:

Tools → texLab

For the first run, select the demo textures under:

/texLab/texLabDemo/Textures/Magma

Choose:

ORM + N [Safe]

Open the Encoded preview and create the outputs.

Once they're created, select the generated texture and check:

Details → Asset User Data

The first workflow should take less than five minutes, without setting up a separate project or switching to an external texture application.

Conclusion 

What started as a relatively straightforward texture-packing tool turned out to involve a much wider set of problems than channel layout alone.

One of the biggest challenges was making sure the preview pipeline matched what is actually written to the .uasset when Create Outputs is executed — rather than maintaining a separate mock-up that could gradually drift from the export path. Another was keeping Manual and Batch on the same unified pack graph, while avoiding unnecessary quality loss by preferring original import sources over already-compressed texture data when building new packs.

A lot of smaller production concerns only became obvious once the tool was used with real assets: ambiguous ORM/RMA/ARM semantics, memory usage and editor responsiveness during batch runs, asset provenance on outputs, and making folder-scale operations safe to execute. A wrong channel order can remain a silent bug until it reaches dozens of materials, which is a large part of why texLab uses explicit semantic rules instead of filename guessing.

Some of these issues weren't obvious when I started. They gradually shaped the architecture: one executor for assembly and repack, dry-run Validate before Execute, provenance on writes, and an Encoded preview that shows the BC7/BC5 round-trip before the result is committed.

In the end, texLab became less about packing textures and more about building a predictable, visible pipeline around that operation. That was probably the most interesting part of developing it.

Links

Pavel Kuzmenko, Technical Artist

Built for the Game & Digital Art Industry
Get Our Media Kit

Comments

0

arrow
Type your comment here
Leave Comment
Built for the Game & Digital Art Industry
Get Our Media Kit