[UE5.8.1] Older materials ignore Opacity Mask and AO when using substrate

Summary

On older materials, Blend Mode of Masked doesn’t work. And there is no setting that you can turn on or off to make it work ever. That material file is now useless. I have no idea what the cause is or when this happened.

If you create a new material from scratch, it works.

What Type of Bug are you experiencing?

Rendering (Graphics / Niagara)

Steps to Reproduce

Not sure what the steps would be. Best guess is to create a material with a single texture to color in an older version of UE. Then in 5.8.1, enable substrate. Then try to use Blend Mode of Masked. Not sure if it’s related to substrate though.

Changing the blend mode to Masked will ignore the Opacity Mask pin. Even if you use a substrate slab, it will still ignore Opacity Mask.

Expected Result

Blend Mode of Masked should work. Setting OpacityMask to 0 should make material invisible.

Observed Result

Mesh (and material preview) stays visible with Opacity Mask of 0 when using Blend Mode of Masked.

Affects Versions

5.8

Platform(s)

Windows

Upload an image

Additional Notes

The HLSL output shows the following on older materials even if you have 0 going into the OpacityMask pin (see attached image):
PixelMaterialInputs.OpacityMask = 1.00000000f;

On a new material created from scratch, the correct value is used:
PixelMaterialInputs.OpacityMask = 0.00000000f;

1 Like

Here is something that was happening to me that I didn’t realize was connected. If you create a material instance of a material that has this issue, the instance will default to Opaque. It seems it doesn’t recognize the change to Blend Mode of Masked in the main material.

If you override the blend mode in the material instance, the instance will then work correctly (while the main material still remains broken).

I found the culprit, but still not sure how it got set. And still need a proper fix. There is a hidden property on the Material called bCanMaskedBeAssumedOpaque. It is set to true. How was it set? I have no idea. One piece of code I saw was that if it was an old UE4 version from FArchive and bIsMasked_DEPRECATED is false (which it usually is), then it would set and write that value as true.

So for this fix, I think you have to resave the file first to stop it from writing true. Then you can run this C++ function on it. This may be overkill, but it works. You can write an Editor Utility Widget to call this on your material if you want.

void UMyLibraryClass::FixMaterial(UMaterial* Material)
{
  if (!Material) return;

  Material->bCanMaskedBeAssumedOpaque = false;
  Material->Modify();

  EBlendMode TargetBlendMode = Material->BlendMode;

  // Switch blend mode to force re-evaluation
  Material->BlendMode = BLEND_Translucent;
  Material->PreEditChange(nullptr);
  Material->PostEditChange();

  // Switch it back.
  Material->BlendMode = TargetBlendMode;
  Material->bCanMaskedBeAssumedOpaque = false;

  Material->PreEditChange(nullptr);
  Material->PostEditChange();
  Material->ForceRecompileForRendering();

  UE_LOG(LogTemp, Log, TEXT("Done material sanitation for: %s"), *Material->GetName());
}

Now that I have a tool to check this flag, I have found that it is on EVERYTHING! Even materials I created not long ago. Some were created in 5.7. Everything I’m using from Fab has the flag. Everything from Megascans has it. I’m surprised this hasn’t come up earlier.

Only materials I’ve created in 5.8 work correctly with Blend Mode of Masked.

Here’s a C++ function to fix all materials in one go. If you get warnings when it runs, that’s fine. It switches to translucent and back to try and force recompiling. Some materials don’t work with translucent blend mode and that’s fine. It will still fix it.

This code was created by Gemini.

Header:

#if WITH_EDITOR
  UFUNCTION(BlueprintCallable)
  static void FixAllMaterials();
#endif

cpp

#if WITH_EDITOR
void UMyLibraryClass::FixAllMaterials()
{
  FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
  IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

  FARFilter Filter;
  Filter.PackagePaths.Add(FName("/Game"));
  Filter.bRecursivePaths = true;
  Filter.ClassPaths.Add(UMaterial::StaticClass()->GetClassPathName());

  TArray<FAssetData> AssetList;
  AssetRegistry.GetAssets(Filter, AssetList);

  int32 FixedCount = 0;

  for (const FAssetData& AssetData : AssetList)
  {
    if (AssetData.AssetClassPath.GetAssetName() == FName("Material"))
    {
      // Instantly loads the object from its registry definition safely
      UMaterial* Material = Cast<UMaterial>(AssetData.GetAsset());

      // Check if the sticky legacy optimization variable is active
      if (Material && Material->bCanMaskedBeAssumedOpaque)
      {
        // Execute our structural blend-mode flip fix we discovered!
        Material->bCanMaskedBeAssumedOpaque = false;
        Material->Modify();

        EBlendMode TrueBlendMode = Material->BlendMode;

        // Loop the layout compiler to force-wipe the legacy data
        Material->BlendMode = BLEND_Translucent;
        Material->PreEditChange(nullptr);
        Material->PostEditChange();

        Material->BlendMode = TrueBlendMode;
        Material->bCanMaskedBeAssumedOpaque = false;
        Material->PreEditChange(nullptr);
        Material->PostEditChange();

        Material->ForceRecompileForRendering();
        FixedCount++;

        UE_LOG(LogTemp, Log, TEXT("Fixed asset: %s"), *Material->GetName());
      }
    }
  }

  UE_LOG(LogTemp, Display, TEXT("--- BATCH FIX COMPLETE --- Processed and saved %d corrupted materials."), FixedCount);
}
#endif

Then just create an Editor Blueprint Widget. Add a button and have it call this function. Run the widget and click the button. It will fix all your materials in one go.

It only fixes the ones with that flag. If it doesn’t have the flag, it skips it.