在这篇文章中,我们将学习如何从 C++ 代码创建新材质、创建节点和创建连接。
您可以将此代码添加到自定义插件中,以便在用户单击编辑器按钮时神奇地创建新材质。
请记住,此代码旨在在编辑器中执行。您的插件必须是编辑器插件。此代码在运行时无法工作...
图解步骤
1、新建一个插件
2、进入VS编写代码,把文章后面的完整代码放入Test.cpp的PluginButtonClicked函数里面(注意我的插件名字叫Test,你的可能和我不一样)。还有记得添加相关头文件和相应模块,要不然编译失败。
3、好像需要重新启动虚幻编辑器,要不然没有效果。重新启动以后,先导入一张图片,然后点击我们创建的按钮。
4、发现材质创建了,双击材质。
5、节点都在一起,可以拖开进行查看效果。
详细步骤
创建一个新资产:材质
首先,我们必须以编程方式创建一个新资产,例如,让我们在根目录文件夹“/Game/”中创建一个名为“M_Material”的新材质。
为此,我们需要创建一个UPackage(这是我们资产的存储对象)。然后,我们使用一个材质工厂来创建我们的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);
然后,我们必须让 Unreal 做一些关于资产创建的后台处理,并加载/设置我们刚刚创建的包。如果没有这个代码,资产创建就无法完成,并且可能会在以后带来一些问题……
FAssetRegistryModule::AssetCreated(UnrealMaterial);
Package->FullyLoad();
Package->SetDirtyFlag(true);
现在,我们的资产已创建但为空。
最后,一旦我们创建了材质并且——也许——添加了节点,让材质自行更新:
// 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;
您可以测试此代码以检查新的空材质创建。
UMaterialExpression
我们将添加的每个节点都是 UMaterialExpression 的子对象。查看虚幻引擎文档以查看可能的节点。
创建节点后,将其添加到材质表达式中。
基本上,必须将表达式分配给要链接到材质的节点。
我们将使用相同的方式在节点之间创建连接:如果我们要将乘法节点的结果设置为材质的基色,则将乘法节点分配给Material->BaseColor.Expression
这里有些例子:
用节点填充材质资产
将“0”常量分配给镜面反射
让我们创建最简单的节点:常量节点。
创建一个新的 UMaterialExpressionConstant 节点,然后将其添加到材质表达式中,将其赋值为 0,最后将其分配给镜面反射表达式。
UMaterialExpressionConstant* ZeroExpression = NewObject<UMaterialExpressionConstant>(UnrealMaterial);
ZeroExpression->R = 0.0f;
UnrealMaterial->Expressions.Add(ZeroExpression);
UnrealMaterial->Specular.Expression = ZeroExpression;
我们的第一个节点已创建并分配!
将纹理与材质基色连接起来
我们假设您已经将纹理资产放入您的内容文件夹中。
我们需要获取UTexture引用来创建节点。所以我们需要通过路径获取这个资产:
FStringAssetReference DiffuseAssetPath("/Game/T_Texture");
UTexture* DiffuseTexture = Cast(DiffuseAssetPath.TryLoad());
if (DiffuseTexture)
{
...
}
然后,创建一个新的 TextureSample 材质表达式,为其指定我们的纹理并将其与材质基色连接。
// Make texture sampler
UMaterialExpressionTextureSample* TextureExpression = NewObject(UnrealMaterial);
TextureExpression->Texture = DiffuseTexture;
TextureExpression->SamplerType = SAMPLERTYPE_Color;
UnrealMaterial->Expressions.Add(TextureExpression);
UnrealMaterial->BaseColor.Expression = TextureExpression;
使用乘法节点
如果我们想创建一个纹理平铺系统,我们需要将纹理坐标与一些标量参数相乘。
让我们创建一个乘法节点并将其分配给我们的纹理坐标。
// Tiling system
UMaterialExpressionMultiply* Multiply = NewObject<UMaterialExpressionMultiply>(UnrealMaterial);
UnrealMaterial->Expressions.Add(Multiply);
TextureExpression->Coordinates.Expression = Multiply;
将纹理坐标节点分配给乘法节点的 A 参数:
UMaterialExpressionTextureCoordinate* TexCoords = NewObject<UMaterialExpressionTextureCoordinate>(UnrealMaterial);
UnrealMaterial->Expressions.Add(TexCoords);
Multiply->A.Expression = TexCoords;
如您所想,您可以使用相同的方式将其他节点分配给 B 参数。
平铺系统
现在,通过创建 2 个包含 X 和 Y 纹理重复的标量参数来完成平铺系统。
// 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;
完整代码
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;