No texture after using setmaterial on dynamic mesh

Hey everyone,

I’m running into an issue with assigning a texture to a dynamically created mesh. I want to create some mesh then use c++ to generate a texture and assign it to the mesh. This simplified version is just two triangles and I am trying to assign a constant color (I will modify to read a file for my actual application to find RGB values). This seems like an easy task but even after reading through the various similar posts I seem to be at a loss and any help would be greatly appreciated!

Here is the header file:

#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "MyProceduralMesh.generated.h"

UCLASS()
class MYPROJECT10_API AMyProceduralMesh : public AActor
{
	GENERATED_BODY()

		UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh")
		UProceduralMeshComponent* pm;
		
public:
	AMyProceduralMesh();
	UPROPERTY(EditAnywhere)
		UMaterialInterface* Material;
	UPROPERTY()
		UTexture2D* RuntimeTexture;
	UPROPERTY()
		TArray<FVector> vertices;
	UPROPERTY()
		TArray<FVector> normals;
	UPROPERTY()
		TArray<int32> triangles;
	UPROPERTY()
		TArray<FVector2D> uvs;
	UPROPERTY()
		TArray<FLinearColor> vertexColors;
		//TArray<FColor> vertexColors;
	UPROPERTY()
		TArray<FProcMeshTangent> tangents;

	virtual void OnConstruction(const FTransform& Transform) override;
	void setmaterial();
	void BeginPlay();
	void ClearMeshData();
};

And the .cpp file:

// Fill out your copyright notice in the Description page of Project Settings.
#include "MyProceduralMesh.h"
// Sets default values
AMyProceduralMesh::AMyProceduralMesh()
{
	PrimaryActorTick.bCanEverTick = true;

	pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh"));
	pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
}

// Called when the game starts or when spawned
void AMyProceduralMesh::BeginPlay()
{
	Super::BeginPlay();
	//setmaterial();
}


void AMyProceduralMesh::OnConstruction(const FTransform& Transform)
{
	ClearMeshData();
	vertices.Add(FVector(0.0f, 0.0f, 0.0f));
	vertices.Add(FVector(1000.0f, 0.0f, 0.0f));
	vertices.Add(FVector(0.0f, 1000.0f, 0.0f));
	vertices.Add(FVector(1000.0f, 1000.0f, 0.0f));
	
	triangles.Add(0);
	triangles.Add(2);
	triangles.Add(1);
	triangles.Add(3);
	triangles.Add(1);
	triangles.Add(2);

	uvs.Add(FVector2D(0.0f, 0.0f));
	uvs.Add(FVector2D(1.0f, 0.0f));
	uvs.Add(FVector2D(0.0f, 1.0f));
	uvs.Add(FVector2D(1.0f, 1.0f));
	
	pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false);
	setmaterial();
}

void AMyProceduralMesh::setmaterial()
{
	UTexture2D* MyTexture;
	MyTexture = UTexture2D::CreateTransient(64, 64, PF_R8G8B8A8);
	MyTexture->UTexture::UpdateResource();
	FColor* MipData = static_cast<FColor*>(MyTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));	
	for (int32 pixelNum = 0; pixelNum < 64 * 64; ++pixelNum)
	{
		MipData[pixelNum] = FColor(255, 0, 0, 255);
	}
	MyTexture->PlatformData->Mips[0].BulkData.Unlock();
	MyTexture->UpdateResource();
	UMaterialInstanceDynamic* MI = UMaterialInstanceDynamic::Create(pm->GetMaterial(1), this);
	MI->SetTextureParameterValue("Texture", MyTexture);
	pm->SetMaterial(0, MI);
}

void AMyProceduralMesh::ClearMeshData()
{
	vertices.Empty();
	triangles.Empty();
	uvs.Empty();
	normals.Empty();
	vertexColors.Empty();
	tangents.Empty();
}

Could be that you are using pm->GetMaterial(1) then SetMaterial(0…)
Is there a material at index 1?

@SolidGasStudios I’ve changed them both to 0 and still have the problem, good catch though!

Here is a screenshot of what the mesh looks like when imported and the “MaterialInstanceDynamic” material.

Parent material is showing as empty in your screenshot, that might be because of using UMaterialInstanceDynamic::Create though.
. Can you show the primary material that this one is an instance of?
Looks as though the wrong material could have been set in the mesh and isn’t aware of the parameters you are trying to set.

Thanks @SolidGasStudios for the help (I didn’t know that I needed a parent material and to make an instance of that)! Also, I now understand why @eblade had referenced instanced materials, I appreciate that lead too!

I will summarize the process below for anyone looking in the future.

I was able to create and assign a texture by first creating a simple material that I could create a dynamic instance of:

I created a relatively simple material but made sure to right click on the “Texture_Param” (which was an added Texture Object) and converted it to a parameter (for C++ to reference - this is the name for the parameter in SetTextureParameterValue).

Here is my new header file:

#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "Factories/Factory.h"
#include "MyProceduralMesh.generated.h"


UCLASS()
class MYPROJECT10_API AMyProceduralMesh : public AActor
{
	GENERATED_BODY()

		UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh")
		UProceduralMeshComponent* pm;

public:
	AMyProceduralMesh();
	UPROPERTY(EditAnywhere)
		UMaterialInterface* Material;
	UPROPERTY(EditAnywhere)
		UMaterial* UnrealMaterial;
	UPROPERTY(EditAnywhere)
		UMaterialInstanceDynamic* DynMaterial;
	UPROPERTY(EditAnywhere)
		UMaterialExpressionTextureSample* TextureExpression;
	UPROPERTY()
		UTexture2D* MyTexture;
	UPROPERTY()
		TArray<FVector> vertices;
	UPROPERTY()
		TArray<FVector> normals;
	UPROPERTY()
		TArray<int32> triangles;
	UPROPERTY()
		TArray<FVector2D> uvs;
	UPROPERTY()
		TArray<FLinearColor> vertexColors;
	UPROPERTY()
		TArray<FProcMeshTangent> tangents;
	
	virtual void OnConstruction(const FTransform& Transform) override;
	void setmaterial();
	void BeginPlay();
	void ClearMeshData();
};

And my cpp file:

// Fill out your copyright notice in the Description page of Project Settings.
#include "MyProceduralMesh.h"

template <typename ObjClass>
static FORCEINLINE ObjClass* LoadObjFromPath(const FName& Path)
{
	if (Path == NAME_None) return nullptr;

	return Cast<ObjClass>(StaticLoadObject(ObjClass::StaticClass(), nullptr, *Path.ToString()));
}

static FORCEINLINE UMaterial* LoadMaterialFromPath(const FName& Path)
{
	if (Path == NAME_None) return nullptr;
	return LoadObjFromPath<UMaterial>(Path);
}

// Sets default values
AMyProceduralMesh::AMyProceduralMesh()
{
	PrimaryActorTick.bCanEverTick = true;
	pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh"));
	pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
}

// Called when the game starts or when spawned
void AMyProceduralMesh::BeginPlay()
{
	Super::BeginPlay();
}

void AMyProceduralMesh::OnConstruction(const FTransform& Transform)
{
	ClearMeshData();
	vertices.Add(FVector(0.0f, 0.0f, 0.0f));
	vertices.Add(FVector(1000.0f, 0.0f, 0.0f));
	vertices.Add(FVector(0.0f, 1000.0f, 0.0f));
	vertices.Add(FVector(1000.0f, 1000.0f, 0.0f));
	
	triangles.Add(0);
	triangles.Add(2);
	triangles.Add(1);
	triangles.Add(3);
	triangles.Add(1);
	triangles.Add(2);

	uvs.Add(FVector2D(0.0f, 0.0f));
	uvs.Add(FVector2D(1.0f, 0.0f));
	uvs.Add(FVector2D(0.0f, 1.0f));
	uvs.Add(FVector2D(1.0f, 1.0f));
	
	pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false);
	setmaterial();
}

void AMyProceduralMesh::setmaterial()
{
	FString sPath = "/Game/StarterContent/Materials/Sample.Sample";
	UMaterial* BaseMat = LoadMaterialFromPath(*sPath);
	DynMaterial = UMaterialInstanceDynamic::Create(BaseMat, this);
	UnrealMaterial = NewObject<UMaterial>();
	
	MyTexture = UTexture2D::CreateTransient(64, 64, PF_R8G8B8A8);
	MyTexture->UTexture::UpdateResource();
	FColor* MipData = static_cast<FColor*>(MyTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));	
	for (int32 pixelNum = 0; pixelNum < 64 * 64; ++pixelNum)
	{
		MipData[pixelNum] = FColor(round(pixelNum * 255 / 4096), round(pixelNum * 255 / 4096), round(pixelNum * 255 / 4096), 255);
	}
	MyTexture->PlatformData->Mips[0].BulkData.Unlock();
	MyTexture->UpdateResource();
	pm->SetMaterial(0, DynMaterial);
	DynMaterial->SetTextureParameterValue(FName(TEXT("Texture_Param")), MyTexture);
}

void AMyProceduralMesh::ClearMeshData()
{
	vertices.Empty();
	triangles.Empty();
	uvs.Empty();
	normals.Empty();
	vertexColors.Empty();
	tangents.Empty();
}
2 Likes