The goal of the engine changes was to make it so layered materials could opt into rendering features, without needing to manually enable those features in the base material, and to standardize custom outputs (all generate the same define and Get function). A new version of the material translator (system that converts material graphs to HLSL shaders) is being implemented, and the original will eventually be deprecated, so any changes you make to the internals of the translator could potentially break if not also applied to the new version.
As a workaround, it would probably be fairly harmless to simply modify the USH files to ignore the NUM_MATERIAL_OUTPUTS_GETBENTNORMAL define, by switching the places it is referenced to something like “#if 0 && (NUM_MATERIAL_OUTPUTS_GETBENTNORMAL > 0)”, so you can still see where the define was in the code when integrating new versions. You’d want to check each new version for additional references to the define. A USH only change wouldn’t run into issues with new translator work. Some other custom outputs set flags (changing engine behavior) or compiler options, which could cause issues if used for attribute flow purposes, and there is some risk that flags or compiler options will be added to that custom output in the future, but it’s probably a low risk.
The more recommended way to pass custom data would be to define your own custom output. This can be done in a plugin, without modifying the engine itself. In the future, it may be possible to do this fully data driven, without a plugin. A material expression is defined that inherits from UMaterialExpressionCustomOutput, with some boilerplate implementation functions. Here’s what the header “MaterialExpressionAuxDataOutput.h” might look like:
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "MaterialValueType.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "MaterialExpressionAuxDataOutput.generated.h"
/**
* Inert custom output that threads arbitrary per-pixel data through the MaterialAttributes / material-layer interface.
*
* NOTE: You do NOT need to place this node to use the channel. The intended workflow uses Set/GetMaterialAttributes
* pins, rather than the standalone node, but an implementation still needs to be defined.
*/
UCLASS(collapsecategories, hidecategories=Object, MinimalAPI)
class UMaterialExpressionAuxDataOutput : public UMaterialExpressionCustomOutput
{
GENERATED_UCLASS_BODY()
UPROPERTY(meta = (RequiredInput = "true"))
FExpressionInput Input;
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override; // legacy HLSL translator
virtual void Build(MIR::FEmitter& Emitter) override; // new MIR translator (MIR::FEmitter fwd-declared by base header)
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
virtual EMaterialValueType GetInputValueType(int32 InputIndex) override { return MCT_Float4; }
#endif
virtual int32 GetNumOutputs() const override { return 1; }
// FunctionName drives the emitted define NUM_MATERIAL_OUTPUTS_GETAUXDATA and GetAuxData0().
virtual FString GetFunctionName() const override { return TEXT("GetAuxData"); }
virtual FString GetDisplayName() const override { return TEXT("AuxData"); }
};
Here’s the MaterialExpressionAuxDataOutput.cpp source with boilerplate implementations:
#include "MaterialExpressionAuxDataOutput.h"
#if WITH_EDITOR
#include "MaterialCompiler.h"
#include "Materials/MaterialExpressionsToMIRCommon.h"
#include "Materials/MIR/MIREmitter.h"
#include "Materials/MIR/MIRTypes.h"
#endif
UMaterialExpressionAuxDataOutput::UMaterialExpressionAuxDataOutput(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
// Custom outputs have no graph output pin.
Outputs.Reset();
#endif
}
#if WITH_EDITOR
int32 UMaterialExpressionAuxDataOutput::Compile(FMaterialCompiler* Compiler, int32 OutputIndex)
{
// Only reached when this node is placed directly in the base material graph. The layer / MaterialAttributes workflow
// never calls this (it compiles the attribute via CompileMaterialAttributesCustomOutputs -> CompilePropertyEx).
if (Input.GetTracedInput().Expression)
{
return Compiler->CustomOutput(this, OutputIndex, Input.Compile(Compiler));
}
return Compiler->Constant4(0.0f, 0.0f, 0.0f, 0.0f);
}
void UMaterialExpressionAuxDataOutput::Build(MIR::FEmitter& Em)
{
// MIR equivalent of Compile. Name matches the registered AttributeName ("AuxData"), without the "Get".
MIR::FValueRef InputValue = Em.CastToFloat(Em.Input(&Input), 4);
Em.SetCustomOutputs(TEXTVIEW("AuxData"), { &InputValue, 1 }, MIR::EMaterialOutputFrequency::PerPixel);
}
void UMaterialExpressionAuxDataOutput::GetCaption(TArray<FString>& OutCaptions) const
{
OutCaptions.Add(TEXT("Aux Data output"));
}
#endif // WITH_EDITOR
And finally, here is the module implementation, which registers the custom output:
#include "Modules/ModuleManager.h"
#include "MaterialValueType.h"
#include "Materials/MaterialAttributeDefinitionMap.h"
#include "MaterialExpressionAuxDataOutput.h"
DEFINE_LOG_CATEGORY_STATIC(LogAuxDataAttribute, Log, All);
class FAuxDataCustomAttributeModule : public IModuleInterface
{
public:
virtual void StartupModule() override
{
// GUID that uniquely identifies this attribute in every material that references it.
static const FGuid AuxDataAttributeID(0x7E1C4A02, 0x9B3D4F18, 0xA5C6E7D0, 0x1234ABCD);
FMaterialAttributeDefinitionMap::AddCustomAttribute(
AuxDataAttributeID,
UMaterialExpressionAuxDataOutput::StaticClass(),
TEXT("AuxData"), // AttributeName - shown in Set/GetMaterialAttributes pin picker.
TEXT("GetAuxData"), // FunctionName - emits GetAuxData0() + NUM_MATERIAL_OUTPUTS_GETAUXDATA.
0, // OutputIndex.
MCT_Float4, // ValueType - must match the node's GetInputValueType.
FVector4(0, 0, 0, 0), // DefaultValue - the inert default read when unset.
SF_Pixel);
UE_LOG(LogAuxDataAttribute, Log, TEXT("Registered custom material attribute 'AuxData' (%s)."),
*AuxDataAttributeID.ToString(EGuidFormats::Digits));
}
};
IMPLEMENT_MODULE(FAuxDataCustomAttributeModule, AuxDataCustomAttributeModule)
You’ll also need boilerplate .uplugin and .Build.cs files needed for any plugin. Sorry for the late response -- I had to write a test plugin to confirm that everything works as expected!
--Jason
[Attachment Removed]