In this post, we will learn how to create a new material from Cpp code, create nodes and make links.
You can add this code into a custom plugin in order to magically create a new material when the user clicks on an editor button.
Remember, this code is designed to be execute into the editor. Your plugin must be an editor plugin. This code can’t work at runtime…
Create a new asset : Material
First of all, we must create a new asset programmatically, for instance, let’s create a new material with name “M_Material” into the root content folder “/Game/”.
To do this, we need to create an UPackage (which is the storage object of our asset). Then, we use a material factory to create our UMaterial.
FString MaterialBaseName = "M_Material";
FString PackageName = "/Game/";
PackageName += MaterialBaseName;
UPackage* Package = CreatePackage(NULL, *PackageName);
// Create an unreal material asset
auto MaterialFactory = NewObject<UMaterialFactoryNew>();
UMaterial* UnrealMaterial = (UMaterial*)MaterialFactory->FactoryCreateNew(UMaterial::StaticClass(), Package, *MaterialBaseName, RF_Standalone | RF_Public, NULL, GWarn);
Then, we must let Unreal do some background process about asset creation and load/set dirty the package we just created. Without this code, the asset creation can be not finalized and it can bring some issues later…
FAssetRegistryModule::AssetCreated(UnrealMaterial);
Package->FullyLoad();
Package->SetDirtyFlag(true);
Now, our asset is created but empty.
Finally, once we created the material and – maybe – added nodes, let the material update itself :
// Let the material update itself if necessary
UnrealMaterial->PreEditChange(NULL);
UnrealMaterial->PostEditChange();
// make sure that any static meshes, etc using this material will stop using the FMaterialResource of the original
// material, and will use the new FMaterialResource created when we make a new UMaterial in place
FGlobalComponentReregisterContext RecreateComponents;
You can test this code in order to check your new empty material creation.
UMaterialExpression
Every node we will add is a subobject of UMaterialExpression. Check the Unreal Engine documentation to see possible nodes.
Once the node created, add it to the material expressions.
Basically, expressions must be assigned to nodes to link to and to the material.
We will create links between nodes using the same way : if we want to set the result of a multiplication node to the base color of the material, assign the multiplication node to the Material->BaseColor.Expression
Here are some examples:
Fill in material asset with nodes
Assign “0” constant to the specular
Let’s create the simplest possible node : Constant node.
Create a new UMaterialExpressionConstant node, then add it to the material expressions, give it value 0 and finally assign it to the specular expression.
UMaterialExpressionConstant* ZeroExpression = NewObject<UMaterialExpressionConstant>(UnrealMaterial);
ZeroExpression->R = 0.0f;
UnrealMaterial->Expressions.Add(ZeroExpression);
UnrealMaterial->Specular.Expression = ZeroExpression;
Our first node is created and assigned !
Link texture with material base color
We assume you have already your texture asset into your content folder.
We need to get the UTexture reference to create the node. So we need get this asset by path :
FStringAssetReference DiffuseAssetPath("/Game/T_Texture");
UTexture* DiffuseTexture = Cast(DiffuseAssetPath.TryLoad());
if (DiffuseTexture)
{
...
}
Then, create a new TextureSample material expression, assign it our texture and link it with material base color.
// Make texture sampler
UMaterialExpressionTextureSample* TextureExpression = NewObject(UnrealMaterial);
TextureExpression->Texture = DiffuseTexture;
TextureExpression->SamplerType = SAMPLERTYPE_Color;
UnrealMaterial->Expressions.Add(TextureExpression);
UnrealMaterial->BaseColor.Expression = TextureExpression;
Use the multiply node
If we want to create a texture tiling system, we need to multiply the texture coordinates with some scalar parameters.
Let’s create a multiply node and assign it to our texture coordinates.
// Tiling system
UMaterialExpressionMultiply* Multiply = NewObject<UMaterialExpressionMultiply>(UnrealMaterial);
UnrealMaterial->Expressions.Add(Multiply);
TextureExpression->Coordinates.Expression = Multiply;
Assign the texture coordinates node to the A parameter of the multiply node :
UMaterialExpressionTextureCoordinate* TexCoords = NewObject<UMaterialExpressionTextureCoordinate>(UnrealMaterial);
UnrealMaterial->Expressions.Add(TexCoords);
Multiply->A.Expression = TexCoords;
As you think, you can assign an other node to the B parameter using the same way.
Tiling system
Now, finalize out tiling system by creating 2 scalar parameters containing X and Y texture repetitions :
// Tiling
UMaterialExpressionAppendVector* Append = NewObject<UMaterialExpressionAppendVector>(UnrealMaterial);
UnrealMaterial->Expressions.Add(Append);
Multiply->B.Expression = Append;
UMaterialExpressionScalarParameter* XParam = NewObject<UMaterialExpressionScalarParameter>(UnrealMaterial);
UMaterialExpressionScalarParameter* YParam = NewObject<UMaterialExpressionScalarParameter>(UnrealMaterial);
UnrealMaterial->Expressions.Add(XParam);
UnrealMaterial->Expressions.Add(YParam);
XParam->ParameterName = "TextureRepeatX";
XParam->DefaultValue = 1;
YParam->ParameterName = "TextureRepeatY";
YParam->DefaultValue = 1;
Append->A.Expression = XParam;
Append->B.Expression = YParam;
Full code
FString MaterialBaseName = "M_Material";
FString PackageName = "/Game/";
PackageName += MaterialBaseName;
UPackage* Package = CreatePackage(NULL, *PackageName);
// create an unreal material asset
auto MaterialFactory = NewObject<UMaterialFactoryNew>();
UMaterial* UnrealMaterial = (UMaterial*)MaterialFactory->FactoryCreateNew(UMaterial::StaticClass(), Package, *MaterialBaseName, RF_Standalone | RF_Public, NULL, GWarn);
FAssetRegistryModule::AssetCreated(UnrealMaterial);
Package->FullyLoad();
Package->SetDirtyFlag(true);
// Tiling system
UMaterialExpressionMultiply* Multiply = NewObject<UMaterialExpressionMultiply>(UnrealMaterial);
UnrealMaterial->Expressions.Add(Multiply);
// Diffuse
FStringAssetReference DiffuseAssetPath("/Game/T_Texture");
UTexture* DiffuseTexture = Cast<UTexture>(DiffuseAssetPath.TryLoad());
if (DiffuseTexture)
{
// make texture sampler
UMaterialExpressionTextureSample* TextureExpression = NewObject<UMaterialExpressionTextureSample>(UnrealMaterial);
TextureExpression->Texture = DiffuseTexture;
TextureExpression->SamplerType = SAMPLERTYPE_Color;
UnrealMaterial->Expressions.Add(TextureExpression);
UnrealMaterial->BaseColor.Expression = TextureExpression;
// Tiling
TextureExpression->Coordinates.Expression = Multiply;
}
// Tiling
UMaterialExpressionAppendVector* Append = NewObject<UMaterialExpressionAppendVector>(UnrealMaterial);
UnrealMaterial->Expressions.Add(Append);
Multiply->B.Expression = Append;
UMaterialExpressionTextureCoordinate* TexCoords = NewObject<UMaterialExpressionTextureCoordinate>(UnrealMaterial);
UnrealMaterial->Expressions.Add(TexCoords);
Multiply->A.Expression = TexCoords;
UMaterialExpressionScalarParameter* XParam = NewObject<UMaterialExpressionScalarParameter>(UnrealMaterial);
UMaterialExpressionScalarParameter* YParam = NewObject<UMaterialExpressionScalarParameter>(UnrealMaterial);
UnrealMaterial->Expressions.Add(XParam);
UnrealMaterial->Expressions.Add(YParam);
XParam->ParameterName = "TextureRepeatX";
XParam->DefaultValue = 1;
YParam->ParameterName = "TextureRepeatY";
YParam->DefaultValue = 1;
Append->A.Expression = XParam;
Append->B.Expression = YParam;
// let the material update itself if necessary
UnrealMaterial->PreEditChange(NULL);
UnrealMaterial->PostEditChange();
// make sure that any static meshes, etc using this material will stop using the FMaterialResource of the original
// material, and will use the new FMaterialResource created when we make a new UMaterial in place
FGlobalComponentReregisterContext RecreateComponents;
where do you write this code? In the build.cs or inside public or where. Thx a lot!
You can add this code behind an onclick event of a plugin button, for instance.
But I have a lot of errors in Visual Studio. For example the first error here:
auto MaterialFactory = NewObject();
In . It tells me that its not defined. Where I have to define it??
Thx a lot
You can’t use NewObject this way, you need to define the output class of the new object. For instance : NewObject().
extremely helpful thanks
Really awesome! Thanks so much. Why do code snippets ignore their #include lines? Autocomplete never finds them. I always have to hunt in the project, search google, and guess. It’s such a pain trying to find them sometimes. Is there some really easy method I’m missing?
Hello, you can use the Resharper c++ extension. It works like a charm to find correct #includes for a specific missing import.
Brilliant
This is super useful, thanks! But I am having trouble connecting inputs to a material function I generate.
UMaterialExpressionMaterialFunctionCall* func = NewObject(newMaterial);
func->MaterialFunction = visibilityFunction;
auto inputA = NewObject(newMaterial);
inputA ->Collection = referenceToAParameterCollection;
inputA ->SetParameterName(*FString(“ParamName”));
How would I connect inputA to the material function expression func?
Hi. First I want to thank you for this tutorial. I am having trouble to find how to assign single channel from texture (for example) UnrealMaterial->Roughness.Expression = TextureExpression ???? GET GREEN CHANNEL;
Any help?
I get LNK1120 and LNK2019 errors at this part:
auto MaterialFactory = NewObject(); ..
I included:
#include “Factories/MaterialFactoryNew.h”
#include “Materials/Material.h”
#include “AssetRegistryModule.h”
and so on.
What am I missing for includes?
Or is there something I’m doing wrong?
Thnx.
you need add “UnrealEd” in xxx.Build.cs
When you click save. It won’t save it gives the error:
The asset ‘/Game/Materials/’ () failed to save.
Cancel: Stop saving all assets and return to the editor.
Retry: Attempt to save the asset again.
Continue: Skip saving this asset only.
Still trying to figure out why.
Hi,
I made a plugin with a button that executes this code. I also created a “M_Material” material in the Content folder. Same for a “T_Texture” texture.
After executing this code, M_Material stays unchanged. I executed step by step with visual studio debugger, and there was no error. Am I missing something ? Maybe there was some recent breaking changes ? I am using 4.25
Thanks
This code works if you put the following code in the function inside your .Build.cs file:
If (Target.bBuildEditor)
{
PublicDependencyModuleNames.AddRange(new string[] { “UnrealEd”, });
}
Then you don’t need to create an editor Plugin.
I have a question as well: Does anybody know if you can set the nodes’ position with C++? It gets tiresome, dragging all the nodes into a comprehensible structure after every test.
Found the answer to my question: MaterialExpressionEditorX is a variable inside of every MaterialExpression.
How can you create a material Instance from a parent in the editor using a EUW