Skip to main content
Thibaud.be
Participating Frequently
September 13, 2026

Substance 3D for Unreal Engine 5.8: output texture formats are forced by the plugin, the texture Compression Settings are shown but ignored

  • September 13, 2026
  • 0 replies
  • 6 views

Plugin: Substance in UE5, version 5.8.1 (plugin version 72), engine libraries 9.5.1, Marketplace build for Unreal Engine 5.8.2, Windows 64-bit. Reproduced on every version since the UE4 plugin (first reported in 2020: https://community.adobe.com/questions-64/ue4-unchangable-normal-map-compression-settings-unavoidable-compression-artefacts-631499).

Summary

The output textures of a Substance graph instance are regular UTexture2D assets, so the texture editor shows the standard "Compression Settings" dropdown (Default, Normalmap BC5, Grayscale, Masks, HDR, …) and the Oodle options. None of them has any effect on what the plugin renders. The plugin decides the format of every output by itself and overwrites it at every load:

  • output whose channel usage is Normal: BC5
  • single-channel output: L8
  • everything else: DXT1 (BC1)

This is Substance::Helpers::OverwriteSubstancePixelFormatForRuntimeCompression() in SubstanceCoreHelpers.cpp, called from USubstanceGraphInstance::PostLoad() and when outputs are created or enabled. It builds a SubstanceAir::OutputFormat from those three rules and applies it with OutputInstance::overrideFormat(). When the output texture already exists, it does not read the asset's CompressionSettings at all: it takes the texture's current pixel format (UE4FormatToSubstance(Texture->GetPixelFormat())) and re-derives the same rules from it, so whatever was there before is what stays.

The rendered result is then copied straight into the texture's mip data by UpdateSubstanceOutput(); Unreal's texture builder, which is what the Compression Settings drive, is never involved for a runtime render.

Consequences

  • There is no way, in the UI, in the plugin settings, or in Blueprint, to choose the format of an output. The only plugin settings are engine type, default output size, memory budget and the async render budget.
  • Normal maps are always BC5 block-compressed. On large high-frequency outputs (ours are 4096x8192 embossing normals) the block artefacts are visible at close range, and users cannot opt for an uncompressed normal even when they have the video memory for it.
  • The UI actively misleads: changing "Compression Settings" on an output texture looks like it worked (the asset is marked dirty and can be saved), but the next render restores the forced format.

What the framework already supports

SubstanceAir::OutputInstance::overrideFormat() accepts any OutputFormat: raw 8-bit and 16-bit, the whole BC family, per-channel shuffles and the mip-chain policy. The plugin uses this call in exactly one place, to force the three formats above, and never exposes it.

Reproduction

  1. Import any sbsar with a normal output, create a graph instance, open the normal output texture.
  2. Set "Compression Settings" to anything but Normalmap (for instance "UserInterface2D (RGBA)" for an uncompressed texture), save.
  3. Change any input of the graph so it renders, or reopen the project.
  4. The texture is BC5 again (visible in the texture editor header and in UTexture2D::GetPixelFormat()).

Workaround we use

With the plugin built inside the project, one exported function added to USubstanceUtility calls overrideFormat() on a single output by identifier (raw BGRA8 or the plugin's default), and our own code applies it before each render. Note for anyone doing the same: request the raw output in the engine's native RGBA order; the plugin maps it to PF_R8G8B8A8 and copies it verbatim, so asking for a BGRA shuffle swaps red and blue and turns the normal map pink.

Suggested fix

  • Honour the output texture's CompressionSettings when deriving the format override: TC_Normalmap -> BC5, TC_Grayscale/TC_Alpha -> L8, TC_Default/TC_Masks -> DXT1/DXT5, TC_HDR -> RGBA16F, TC_EditorIcon/TC_VectorDisplacementmap (uncompressed) -> raw 8-bit, and derive from the channel usage only when the texture does not exist yet.
  • Or, at minimum, expose an explicit per-output format on the graph instance (or a Blueprint node wrapping overrideFormat) and hide or grey out the Compression Settings that have no effect.