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.
Build Material
.h/** * 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);
.cpp#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; }
.Build.csPrivateDependencyModuleNames.AddRange ( new string[] { // Default Modules "Core", "CoreUObject", "Engine", // New Modules - Editor Only "AssetTools", "UnrealEd", "MaterialEditor", } );
Add TextureParameter node to Material
.h/** * 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);
.cpp#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
.h/** * 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);
.cpp#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
.h/** * 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);
.cpp#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
.h/** * 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);
.cpp#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
.h/** * 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);
.cpp#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
.h/** * 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);
.cppUMaterialExpression* 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
.
.h
/**
* 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);
.cpp
#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;
}