Hi everyone, is it possible to create a custom C++ node for the material editor that will extract a user data from a texture object?
It’s a static value so in the final HLSL code it can be like a const scalar parameter value.
Hi everyone, is it possible to create a custom C++ node for the material editor that will extract a user data from a texture object?
It’s a static value so in the final HLSL code it can be like a const scalar parameter value.
Hi Kevin_Masson,
You can’t call c++ from HLSL - to do that you’d need to parse that user data before-hand, either from c++ or blueprint and then pass it into the material with a parameter (dynamic material instance).
This is what I thought, thanks for confirming this
I managed to create a material expression that reads from the texture UserData. However, the data is extracted at compile time in C++ so it won’t work with material instances when changing the texture input.
Here is the code, hope it can help:
header
#pragma once
#include "CoreMinimal.h"
#include "Materials/MaterialExpression.h"
#include "MaterialExpressionExtractTextureUserData.generated.h"
UCLASS(collapsecategories, hidecategories=Object)
class UMaterialExpressionExtractTextureUserData : public UMaterialExpression
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=MaterialExpressionTextureBase)
UTexture* Texture;
/**
* Texture object input which overrides Texture if specified.
* This only shows up in material functions and is used to implement texture parameters without actually putting the texture parameter in the function.
*/
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'Texture' if not specified"))
FExpressionInput TextureObject;
//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual uint32 GetInputType(int32 InputIndex) override;
#endif // WITH_EDITOR
//~ End UMaterialExpression Interface
/**
* Callback to get any texture reference this expression emits.
* This is used to link the compiled uniform expressions with their default texture values.
* Any UMaterialExpression whose compilation creates a texture uniform expression (eg Compiler->Texture, Compiler->TextureParameter) must implement this.
*/
virtual UObject* GetReferencedTexture() const override;
virtual bool CanReferenceTexture() const override { return true; }
};
source
#include "MaterialExpressionExtractTextureUserData.h"
#if WITH_EDITOR
#include "MaterialCompiler.h"
#endif
#define LOCTEXT_NAMESPACE "CustomMaterialExpression"
UMaterialExpressionExtractTextureUserData::UMaterialExpressionExtractTextureUserData(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Texture(nullptr)
{
#if WITH_EDITORONLY_DATA
bCollapsed = true;
#endif
}
#if WITH_EDITOR
int32 UMaterialExpressionExtractTextureUserData::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
UE_LOG(LogTemp, Warning, TEXT("Compiling..."));
const UMaterialExpression* InputExpression = TextureObject.GetTracedInput().Expression;
if (Texture == nullptr && InputExpression == nullptr)
{
return CompilerError(Compiler, TEXT("Missing input texture"));
}
if (InputExpression != nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("Has expression..."));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Has texture..."));
}
int32 TextureReferenceIndex = INDEX_NONE;
int32 TextureCodeIndex = INDEX_NONE;
if (InputExpression)
{
TextureCodeIndex = TextureObject.Compile(Compiler);
}
else
{
TextureCodeIndex = Compiler->ExternalTexture(Texture, TextureReferenceIndex);
}
if (TextureCodeIndex == INDEX_NONE)
{
// Can't continue without a texture
UE_LOG(LogTemp, Warning, TEXT("No texture to sample"));
return INDEX_NONE;
}
const UTexture* EffectiveTexture = Texture;
EMaterialSamplerType EffectiveSamplerType = SAMPLERTYPE_External;
if (InputExpression)
{
TOptional<FName> EffectiveParameterName;
if (!Compiler->GetTextureForExpression(TextureCodeIndex, TextureReferenceIndex, EffectiveSamplerType, EffectiveParameterName))
{
return CompilerError(Compiler, TEXT("Tex input requires a texture value"));
}
if (TextureReferenceIndex != INDEX_NONE)
{
EffectiveTexture = Cast<UTexture>(Compiler->GetReferencedTexture(TextureReferenceIndex));
}
}
if (EffectiveTexture->IsValidLowLevel())
{
//-==================================== EXTRACT DATA HERE
return Compiler->Constant(SomeData);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("No effective texture!"));
}
return Compiler->Constant(0);
}
uint32 UMaterialExpressionExtractTextureUserData::GetInputType(int32 InputIndex)
{
return MCT_Texture;
}
#endif
UObject* UMaterialExpressionExtractTextureUserData::GetReferencedTexture() const
{
return Texture;
}
Yeah to get it to work dynamically you’ll need to wrap it in a blueprint - that would need to call the c++ method to obtain the textures asset user data (unless someone knows how to get it from blueprint?) and pass that into a “Dynamic Material Instance”.
If you’re in control of that asset user data, perhaps another way of doing it could be with metadata:
Hey man, let me know if you can help me with my topic: