Custom material node to extract UTexture2D data in C++

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.

2 Likes

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).

2 Likes

This is what I thought, thanks for confirming this :ok_hand:

1 Like

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;
}

1 Like

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:

1 Like

Hey man, let me know if you can help me with my topic: