Why does dynamic material have no effect?

Hi fellow game developers!

I have been working on a c++ class that would draw 2D graph on a 3D object from discrete data. Since Material Editor has no loops or arrays, I see no other choice. I have been mostly following this tutorial:
A new, community-hosted Unreal Engine Wiki - Announcements - Unreal Engine Forums,
but I have left out some “lava specific” stuff and used AActor class instead of StaticMesh actor.

What I have been trying to do so far:
-create an actor,
-create a cube and set it as root component (so far it works),
-create a dynamic texture, apply it to a dynamic material and the material to the cube.

My problem:
When I set the material to my cube, nothing happens. I have tried many different things from many tutorials and no matter what I do, my cube looks the same.

Here is what I have so far:

Header file:

UCLASS()
class REACTIVITYGAME_API AScrollingGraph : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AScrollingGraph(const FObjectInitializer& ObjectInitializer);

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Graph)
		UTexture2D* DynTexture;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Graph)
		UMaterialInstanceDynamic* DynMaterial;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Graph)
	class UStaticMeshComponent* CanvasBG;

	virtual void PostInitializeComponents() override;

protected:
	void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);

	UFUNCTION(BlueprintCallable, Category = Graph)
		void CreateBgTexture();
	void CreateBgMaterial();

	UFUNCTION(BlueprintCallable, Category = Graph)
		void UpdateBgTexture();

private:
	UPROPERTY()
		TArray<FColor> data;
	FUpdateTextureRegion2D *bgUpdateTextureRegion;
	//Assets
	UStaticMesh * AssetSM_CanvasBG;
	UMaterial * AssetMat_CanvasBG;
};

C++ file:

AScrollingGraph::AScrollingGraph(const FObjectInitializer &ObjectInitializer) :Super(ObjectInitializer)
{
	//Find static mesh asset
	static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshOb_CU1(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));
	if (StaticMeshOb_CU1.Succeeded()) AssetSM_CanvasBG = StaticMeshOb_CU1.Object;
	//Find a Material asset
	//static ConstructorHelpers::FObjectFinder<UMaterial> MaterialOb_CU1(TEXT("Material'/Engine/EngineMaterials/DefaultMaterial.DefaultMaterial'"));
	//if (MaterialOb_CU1.Succeeded()) AssetMat_CanvasBG = MaterialOb_CU1.Object;

	//Create Object and set a static mesh
	CanvasBG = ObjectInitializer.CreateDefaultSubobject < UStaticMeshComponent >(this, TEXT("Cube_BG"));
	
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AScrollingGraph::BeginPlay()
{
	Super::BeginPlay();
	UWorld* world = GetWorld();
	//if (world)
	if (true)//should be word, true is for debugging
	{
		//Create a texture
		CreateBgTexture();
		DynMaterial = CanvasBG->CreateAndSetMaterialInstanceDynamic(0);
		CanvasBG->SetStaticMesh(AssetSM_CanvasBG);
		RootComponent = CanvasBG;

		//DynMaterial = UMaterialInstanceDynamic::Create(AssetMat_CanvasBG, NULL);

		//CanvasBG->SetMaterial(0, DynMaterial);
               //Also tried using this to create and assing material, same result
	}
}

// Called every frame
void AScrollingGraph::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );
	UpdateBgTexture();
}

void AScrollingGraph::CreateBgTexture() {
	// Create echo texture
	DynTexture = UTexture2D::CreateTransient(BG_TEXTURE_SIZEX, BG_TEXTURE_SIZEY);
	DynTexture->SRGB = 0;
	DynTexture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
	DynTexture->AddToRoot();
	DynTexture->UpdateResource();

	bgUpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, BG_TEXTURE_SIZEX, BG_TEXTURE_SIZEY);  // Note: This never gets freed
	// Initialize data
	data.Init(FColor(0, 20, 255, 255), BG_TEXTURE_DATA_LENGHT);
}

void AScrollingGraph::UpdateBgTexture() {
	UpdateTextureRegions(DynTexture, (int32)0, (uint32)1, bgUpdateTextureRegion, (uint32)(4 * BG_TEXTURE_SIZEX), (uint32)4, (uint8*)data.GetData(), false);
	DynMaterial->SetTextureParameterValue(FName(TEXT("DynamicTexture")), DynTexture);
}

void AScrollingGraph::PostInitializeComponents() {
	Super::PostInitializeComponents();
	CreateBgTexture();
	DynMaterial = CanvasBG->CreateAndSetMaterialInstanceDynamic(0);
	CanvasBG->SetStaticMesh(AssetSM_CanvasBG);
	RootComponent = CanvasBG;
}

My problem: Nothing I try has any effect whatsoever. Even If I try to load a material that already exists and assing it using

static ConstructorHelpers::FObjectFinder<UMaterial> MaterialOb_CU1(TEXT("Material'/Engine/EngineMaterials/DefaultMaterial.DefaultMaterial'"));
     if (MaterialOb_CU1.Succeeded()) AssetMat_CanvasBG = MaterialOb_CU1.Object;
DynMaterial = UMaterialInstanceDynamic::Create(AssetMat_CanvasBG, NULL);
CanvasBG->SetMaterial(0, DynMaterial);

Nothing happens. I must be missing something really obvious. I don’t want to admit how much time I have already spent on this material assigning from c++ thing, but way too much.
Thanks in advance guys!

Hello ,

This is a subject I’m trying to learn more about myself so I’ll try to help out. First thing I’d do is pause the game while it’s playing and check to make sure the material instance is actually being assigned properly. I’ll be looking closer at the code in the meantime.

Hello! I think it is getting set, since during “gameplay”, I can see this in the “Details” panel

Thank you for checking that, and actually that screenshot tells me a lot more. Are you expecting to see the cube change to that Blue texture’s color when you set the material? From your current code, it seems like you have almost everything covered except for actually applying the texture. To give a visual representation, this is currently what you have:

The texture is in the material, but hasn’t actually been applied to the material. You’ll need to set it to one of those inputs, such as Base Color. It seems like you can set this as a variable on the base material, instead of the instance itself. Could you give that a try?

Hello!
Thanks a lot for your response. As you suggested, I have tried setting some properties, like:

	DynMaterial->SetTextureParameterValue(FName(TEXT("DynamicTexture")), DynTexture);
	DynMaterial->SetTextureParameterValue(FName(TEXT("BaseColor")), DynTexture);
	DynMaterial->SetTextureParameterValue(FName(TEXT("EmissiveColor")), DynTexture);

When I try to get material details now, I see this:

My cube still stays the same tho. I must be missing something really obvious. If I change Material parent in the details view, it gets applied, but my texture still has no effect.

If you wish to have these parameters be applied, you’ll need to link them up in the Material Editor. If you want your texture to cover the entire material, you can just plug “DynamicTexture” into the “Base Color” input of the material’s main node (the brown colored one in my previous screenshot). The “BaseColor” variable I was referring to previously can only be accessed directly in C++ through a UMaterial instead of a UMaterialInstanceDynamic so using this may work without needing to mess with the material editor:

DynMaterial->GetBaseMaterial()->BaseColor = MyTextureParameter;

Thanks for the quick response! Would you mind giving me a hint on how to pass the parameter? BaseColor seems to have type “FExpressionInput”, but I’m really confused at the moment and can’t figure out how to pass a Texture to it.

I’m still looking into how to do this directly through code due to terminology and such but to get this set up with some help from the material editor, which will most likely be much easier either way, you can do it like this.

I have an actor that has 5 variables.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Mesh)
UStaticMeshComponent* MyMesh;

UMaterialInstanceDynamic* MyMaterialDynamic;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Mesh)
UMaterial* MyMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Mesh)
UTexture* MyTexture;

FName TextureParamName = FName(TEXT("TextureParam"));

You can make the UMaterial and UTexture into UPROPERTYs like I did if you wish so you can just set them in the editor instead of worrying about setting them in the constructor. Either way works.

I then have the following code in my BeginPlay.

MyMaterialDynamic = UMaterialInstanceDynamic::Create(MyMaterial, this);
MyMesh->SetMaterial(0, MyMaterialDynamic);
MyMaterialDynamic->SetTextureParameterValue(TextureParamName, MyTexture);

After that, all that is needed is to set up the in-editor stuff. I created a Blueprint based off the class, added an instance to the viewport, set MyMesh to a cube, MyMaterial to a new material that I created, and MyTexture to one from the StarterContent pack. I then opened the Material editor for my new material and added a TextureSampleParameter2D node. After adding said node, I changed its “Parameter Name” to match the exact FName that I had set up in code, which was TextureParam. I then plugged it into the Base Color input like so.

Note: The default texture will continue to show for the TextureParam node unless you set it to something else, but it’ll be overridden by the BeginPlay code if done correctly.

After doing all this, upon pressing “Play” my cube gets a fresh coat of paint and looks correct.