Working with Material in Unreal Engine

How to work with materials in the Unreal Engine?

Create Material Graph with C++

Creating Material Graph with C++ instead of using Blueprint nodes make sense especially to automate processes when working with a lot of materials. C++ is a way to move material data exported from any tool to the Unreal Engine and re-create them aitomatically to materials directly by code.

C++ comes it handy as well anytime there's a need to add a functionality that is not available through the common BP nodes.

  • All Material extensions
  • #include "MaterialEditingLibrary.h" // MaterialEditor (Editor Only) is required for working with Materials in C++ in the editor

All the code below is placed in UBlueprintFunctionLibrary inherited class.

The screenshot above is the output of the code below

  • Build Material

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Build a material graph with the provided values
     *
     * @param MaterialPath	The path of the material: "/Game/Folder/MyMaterial"
     * @param TexturePath	The path of the texture to add in the material: "/Game/Folder/MyTexture"
     * @param TexCoord	Texture coordinates for the texture
     * @param Color	Color that will be multiplied by the texture
     * @param Metallic	Metallic value of the material
     * @param Specular	Specular value of the material
     * @param Roughness	Roughness value of the material
     * @param OutInfoMsg	More information about the action's result
     * 
     * @return If the action was a success or not
     */
    UFUNCTION(BlueprintCallable, Category = "")
    static bool BuildMaterial(FString MaterialPath, FString TexturePath, FVector2D TexCoord, FLinearColor Color, float Metallic, float Specular, float Roughness, FString& OutInfoMsg);
    #include "MaterialEditingLibrary.h" // MaterialEditor (Editor Only)
    #include "Materials/MaterialExpressionTextureSampleParameter2D.h" // Engine
    #include "Materials/MaterialExpressionTextureCoordinate.h" // Engine
    #include "Materials/MaterialExpressionMultiply.h" // Engine
    #include "Materials/MaterialExpressionVectorParameter.h" // Engine
    
    bool UExampleClass::BuildMaterial(FString MaterialPath, FString TexturePath, FVector2D TexCoord, FLinearColor Color, float Metallic, float Specular, float Roughness, FString& OutInfoMsg)
    {
      // Get material and texture
      UMaterial* Material = Cast<UMaterial>(StaticLoadObject(UObject::StaticClass(), nullptr, *MaterialPath));
      UTexture* Texture = Cast<UTexture>(StaticLoadObject(UObject::StaticClass(), nullptr, *TexturePath));
    
      // Create material if it doesn't exist
      if (Material == nullptr)
      {
        Material = UExampleClass::CreateMaterialAsset(MaterialPath, bOutSuccess, OutInfoMsg);
        if (Material == nullptr) return false; // Failed to create material
      }
    
      // Create nodes
      UMaterialExpressionTextureCoordinate* TextureCoordinateExp = AddTexCoordExpression(Material, TexCoord, "MyTextureCoordinates", FIntPoint(-1000, -50));
      UMaterialExpressionTextureSampleParameter2D* TextureExp = AddTextureParameter(Material, Texture, "MyTexture", FIntPoint(-800, -50));
      UMaterialExpressionVectorParameter* ColorExp = AddVectorParameter(Material, Color, "MyColor", FIntPoint(-800, 200));
      UMaterialExpressionMultiply* ColorMultiplyExp = AddMultiplyExpression(Material, "MyColorMultiply", FIntPoint(-500, 0));
      UMaterialExpressionScalarParameter* MetallicScalarExp = AddScalarParameter(Material, Metallic, "MyMetallic", FIntPoint(-300, 50));
      UMaterialExpressionScalarParameter* SpecularScalarExp = AddScalarParameter(Material, Specular, "MySpecular", FIntPoint(-300, 150));
      UMaterialExpressionScalarParameter* RoughnessScalarExp = AddScalarParameter(Material, Roughness, "MyRoughness", FIntPoint(-300, 250));
    
      // Connect nodes
      TextureCoordinateExp->ConnectExpression(&TextureExp->Coordinates, 0);
      TextureExp->ConnectExpression(&ColorMultiplyExp->A, 0);
      ColorExp->ConnectExpression(&ColorMultiplyExp->B, 0);
    
      // Connect to main material node
      Material->GetEditorOnlyData()->BaseColor.Connect(0, ColorMultiplyExp);
      Material->GetEditorOnlyData()->Metallic.Connect(0, MetallicScalarExp);
      Material->GetEditorOnlyData()->Specular.Connect(0, SpecularScalarExp);
      Material->GetEditorOnlyData()->Roughness.Connect(0, RoughnessScalarExp);
    
      OutInfoMsg = FString::Printf(TEXT("Build Material Succeeded - '%s'"), *MaterialPath);
      return true;
    }
    PrivateDependencyModuleNames.AddRange
    (
      new string[] 
      { 
        // Default Modules
        "Core", 
        "CoreUObject", 
        "Engine", 
    
        // New Modules - Editor Only
        "AssetTools",
        "UnrealEd",
        "MaterialEditor",
      }
    );
  • Add TextureParameter node to Material

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Add a texture parameter to a material graph
     *
     * @param Material	The material in which to add the expression
     * @param Texture	The texture to use
     * @param ParameterName	The name of the expression
     * @param NodePos	The XY coordinates of the node in the graph
     *
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = "")
    static class UMaterialExpressionTextureSampleParameter2D* AddTextureParameter(UMaterial* Material, UTexture* Texture, FString ParameterName, FIntPoint NodePos);
    #include "Materials/MaterialExpressionTextureSampleParameter2D.h" // Engine
    
    UMaterialExpressionTextureSampleParameter2D* UExampleClass::AddTextureParameter(UMaterial* Material, UTexture* Texture, FString ParameterName, FIntPoint NodePos)
    {
     // Get existing parameter from material
     UMaterialExpressionTextureSampleParameter2D* TextureParameter = Cast<UMaterialExpressionTextureSampleParameter2D>(GetExistingMaterialExpressionFromName(Material, ParameterName));
    
     // Create new parameter if it doesn't exist
     if (TextureParameter == nullptr)
     {
       UMaterialExpression* Expression = UMaterialEditingLibrary::CreateMaterialExpression(Material, UMaterialExpressionTextureSampleParameter2D::StaticClass());
       TextureParameter = Cast<UMaterialExpressionTextureSampleParameter2D>(Expression);
       Material->AddExpressionParameter(TextureParameter, Material->EditorParameters);
     }
    
     // Set name and position
     TextureParameter->ParameterName = *ParameterName;
     TextureParameter->MaterialExpressionEditorX = NodePos.X;
     TextureParameter->MaterialExpressionEditorY = NodePos.Y;
     // Set value
     TextureParameter->Texture = Texture;
     TextureParameter->SamplerType = SAMPLERTYPE_Color;
    
     // Return parameter
     return TextureParameter;
    }
  • Add ScalarParameter node to Material

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Add a scalar parameter to a material graph
     *
     * @param Material	The material in which to add the expression
     * @param Value	The float value to use
     * @param ParameterName	The name of the expression
     * @param NodePos	The XY coordinates of the node in the graph
     *
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = " ")
    static class UMaterialExpressionScalarParameter* AddScalarParameter(UMaterial* Material, float Value, FString ParameterName, FIntPoint NodePos);
    #include "Materials/MaterialExpressionScalarParameter.h" // Engine
    
    UMaterialExpressionScalarParameter* UExampleClass::AddScalarParameter(UMaterial* Material, float Value, FString ParameterName, FIntPoint NodePos)
    {
      // Get existing parameter from material
      UMaterialExpressionScalarParameter* ScalarParameter = Cast<UMaterialExpressionScalarParameter>(GetExistingMaterialExpressionFromName(Material, ParameterName));
    
      // Create new parameter if it doesn't exist
      if (ScalarParameter == nullptr)
      {
        UMaterialExpression* Expression = UMaterialEditingLibrary::CreateMaterialExpression(Material, UMaterialExpressionScalarParameter::StaticClass());
        ScalarParameter = Cast<UMaterialExpressionScalarParameter>(Expression);
        Material->AddExpressionParameter(ScalarParameter, Material->EditorParameters);
      }
    
      // Set name and position
      ScalarParameter->ParameterName = *ParameterName;
      ScalarParameter->MaterialExpressionEditorX = NodePos.X;
      ScalarParameter->MaterialExpressionEditorY = NodePos.Y;
      // Set value
      ScalarParameter->DefaultValue = Value;
    
      // Return parameter
      return ScalarParameter;
    }
  • Add VectorParameter node to Material

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Add a vector parameter to a material graph
     *
     * @param Material	The material in which to add the expression
     * @param Color	The color value to use
     * @param ParameterName	The name of the expression
     * @param NodePos	The XY coordinates of the node in the graph
     *
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = "Alex Quevillon|10 - Build Material")
    static class UMaterialExpressionVectorParameter* AddVectorParameter(UMaterial* Material, FLinearColor Color, FString ParameterName, FIntPoint NodePos);
    #include "Materials/MaterialExpressionVectorParameter.h" // Engine
    
    UMaterialExpressionVectorParameter* UExampleClass::AddVectorParameter(UMaterial* Material, FLinearColor Color, FString ParameterName, FIntPoint NodePos)
    {
      // Get existing parameter from material
      UMaterialExpressionVectorParameter* VectorParameter = Cast<UMaterialExpressionVectorParameter>(GetExistingMaterialExpressionFromName(Material, ParameterName));
    
      // Create new parameter if it doesn't exist
      if (VectorParameter == nullptr)
      {
        UMaterialExpression* Expression = UMaterialEditingLibrary::CreateMaterialExpression(Material, UMaterialExpressionVectorParameter::StaticClass());
        VectorParameter = Cast<UMaterialExpressionVectorParameter>(Expression);
        Material->AddExpressionParameter(VectorParameter, Material->EditorParameters);
      }
    
      // Set name and position
      VectorParameter->ParameterName = *ParameterName;
      VectorParameter->MaterialExpressionEditorX = NodePos.X;
      VectorParameter->MaterialExpressionEditorY = NodePos.Y;
      // Set value
      VectorParameter->DefaultValue = Color;
    
      // Return parameter
      return VectorParameter;
    }
  • Add MultiplyExpression node to Material

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Add a multiply expression to a material graph
     *
     * @param Material	The material in which to add the expression
     * @param ExpressionDesc	The description of the expression
     * @param NodePos	The XY coordinates of the node in the graph
     *
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = " ")
    static class UMaterialExpressionMultiply* AddMultiplyExpression(UMaterial* Material, FString ExpressionDesc, FIntPoint NodePos);
    #include "Materials/MaterialExpressionMultiply.h" // Engine
    
    UMaterialExpressionMultiply* UExampleClass::AddMultiplyExpression(UMaterial* Material, FString ExpressionDesc, FIntPoint NodePos)
    {
    	// Get existing expression from material
    	UMaterialExpressionMultiply* MultiplyExpression = Cast<UMaterialExpressionMultiply>(GetExistingMaterialExpressionFromName(Material, ExpressionDesc));
    	
    	// Create new expression if it doesn't exist
    	if (MultiplyExpression == nullptr)
    	{
    		UMaterialExpression* Expression = UMaterialEditingLibrary::CreateMaterialExpression(Material, UMaterialExpressionMultiply::StaticClass());
    		MultiplyExpression = Cast<UMaterialExpressionMultiply>(Expression);
    		Material->AddExpressionParameter(MultiplyExpression, Material->EditorParameters);
    	}
    
    	// Set name and position
    	MultiplyExpression->Desc = ExpressionDesc;
    	MultiplyExpression->MaterialExpressionEditorX = NodePos.X;
    	MultiplyExpression->MaterialExpressionEditorY = NodePos.Y;
    	
    	// Return expression
    	return MultiplyExpression;
    }
  • Add TexCoordExpression node to Material

    	/**
     * Editor Only - Will not work in packaged build.
     *
     * Add a texture coordinate expression to a material graph
     *
     * @param Material	The material in which to add the expression
     * @param Value	The texture coordinates value to use
     * @param ExpressionDesc	The description of the expression
     * @param NodePos	The XY coordinates of the node in the graph
     *
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = "Alex Quevillon|10 - Build Material")
    static class UMaterialExpressionTextureCoordinate* AddTexCoordExpression(UMaterial* Material, FVector2D Value, FString ExpressionDesc, FIntPoint NodePos);
    #include "Materials/MaterialExpressionTextureCoordinate.h" // Engine
    
    UMaterialExpressionTextureCoordinate* UExampleClass::AddTexCoordExpression(UMaterial* Material, FVector2D Value, FString ExpressionDesc, FIntPoint NodePos)
    {
    	// Get existing expression from material
    	UMaterialExpressionTextureCoordinate* TextureCoordinateExpression = Cast<UMaterialExpressionTextureCoordinate>(GetExistingMaterialExpressionFromName(Material, ExpressionDesc));
    
    	// Create new expression if it doesn't exist
    	if (TextureCoordinateExpression == nullptr)
    	{
    		UMaterialExpression* Expression = UMaterialEditingLibrary::CreateMaterialExpression(Material, UMaterialExpressionTextureCoordinate::StaticClass());
    		TextureCoordinateExpression = Cast<UMaterialExpressionTextureCoordinate>(Expression);
    		Material->AddExpressionParameter(TextureCoordinateExpression, Material->EditorParameters);
    	}
    
    	// Set name and position
    	TextureCoordinateExpression->Desc = ExpressionDesc;
    	TextureCoordinateExpression->MaterialExpressionEditorX = NodePos.X;
    	TextureCoordinateExpression->MaterialExpressionEditorY = NodePos.Y;
    	// Set value
    	TextureCoordinateExpression->UTiling = Value.X;
    	TextureCoordinateExpression->VTiling = Value.Y;
    
    	// Return expression
    	return TextureCoordinateExpression;
    }
  • GetExistingMaterialExpressionFromName helpe function

    /**
     * Editor Only - Will not work in packaged build.
     *
     * Retrieve an existing material expression based on the provided name or description
     *
     * @param Material	The material in which the expression is
     * @param NameOrDescription	The name or description of the expression to find
     * 
     * @return The expression
     */
    UFUNCTION(BlueprintCallable, Category = " ")
    static class UMaterialExpression* GetExistingMaterialExpressionFromName(UMaterial* Material, FString NameOrDescription);
    UMaterialExpression* UExampleClass::GetExistingMaterialExpressionFromName(UMaterial* Material, FString NameOrDescription)
    {
     for (UMaterialExpression* Expression : Material->GetExpressions())
     {
       UMaterialExpressionParameter* Parameter = Cast<UMaterialExpressionParameter>(Expression);
       if (Parameter != nullptr && Parameter->ParameterName.ToString() == NameOrDescription)
       {
         return Parameter;
       }
       else if (Expression->Desc == NameOrDescription)
       {
          return Expression;
       }
     }
     return nullptr;
    }

Assign Material to Mesh in C++

The code below allow to assign a material to Static Mesh as well as to Skeletal Mesh.

/**
 * Editor Only - Will not work in packaged build.
 *
 * Assign material to a mesh asset (static mesh or skeletal mesh)
 *
 * @param MeshPath	Path of the mesh: "/Game/Folder/MyMesh"
 * @param MaterialPath	Path of the material: "/Game/Folder/MyMaterial"
 * @param MaterialId	Material slot to assign material into
 * @param OutInfoMsg	More information about the action's result
 *
 * @return If the action was a success or not
 */
UFUNCTION(BlueprintCallable, Category = "Alex Quevillon | Assign Material To Mesh")
static bool AssignMaterialToMeshAsset(FString MeshPath, FString MaterialPath, int MaterialId, FString& OutInfoMsg);
#include "Engine/StaticMesh.h" // Engine
#include "Engine/SkeletalMesh.h" // Engine

bool UExampleClass::AssignMaterialToMeshAsset(FString MeshPath, FString MaterialPath, int MaterialId, FString& OutInfoMsg)
{
  // Load material
  UMaterialInterface* Material = Cast<UMaterialInterface>(StaticLoadObject(UObject::StaticClass(), nullptr, *MaterialPath));
  // Load mesh
  UObject* PotentialMesh = StaticLoadObject(UObject::StaticClass(), nullptr, *MeshPath);
  UStaticMesh* StaticMesh = Cast<UStaticMesh>(PotentialMesh);
  USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(PotentialMesh);

  if (StaticMesh != nullptr)
  {
    // Apply material to static mesh
    int RealMaterialId = FMath::Clamp(MaterialId, 0, StaticMesh->GetStaticMaterials().Num() - 1);
    StaticMesh->SetMaterial(RealMaterialId, Material);
    StaticMesh->PostEditChange();
  }
  else if (SkeletalMesh != nullptr)
  {
    // Apply material to skeletal mesh
    int RealMaterialId = FMath::Clamp(MaterialId, 0, SkeletalMesh->GetMaterials().Num() - 1);
    SkeletalMesh->GetMaterials()[RealMaterialId] = Material;
    SkeletalMesh->PostEditChange();
  }
  else
  {
    OutInfoMsg = FString::Printf(TEXT("Assign Material To Mesh Asset Failed - Mesh isn't valid. '%s'"), *MeshPath);
    return false;
  }

  OutInfoMsg = FString::Printf(TEXT("Assign Material To Mesh Asset Succeeded - '%s'"), *MeshPath);
  return true;
}