UE4:在编辑器下对材质函数展开的方法

需求背景

TA使用了大量的MaterialFunction,这次是需要在已经封装调用了MaterialFunction的地方当场临时展开方便查看一些问题,想要一个编辑器接口。

本次功能实现由小伙伴友情提供,这里学习一个。

函数概述

实现了一个ExpandingMaterialFunction函数,其主要功能是将材质中的指定材质函数展开,将材质函数的内部节点复制到材质中,并重新连接输入和输出。函数的签名如下:

1
void UEdLibrary::ExpandingMaterialFunction(UMaterial* Material, const FString FuncName, const int32 OffsetX, const int32 OffsetY)

参数说明:

  • Material:要进行展开操作的材质对象。
  • FuncName:要展开的材质函数的名称。
  • OffsetXOffsetY:展开后的节点在材质编辑器中的位置偏移量。

函数实现步骤

1、查找材质函数节点

函数首先遍历材质的所有表达式节点,找到名称与给定的FuncName匹配的材质函数节点。这里使用了UMaterialExpressionMaterialFunctionCall类来判断节点是否为材质函数调用节点。找到的材质函数节点会被存储在MaterialFunctionArrayMaterialExpressionFuncArr数组中,同时记录节点在材质编辑器中的位置。

2、复制材质函数的内部节点

对于每个找到的材质函数节点,函数会复制其内部的所有表达式节点到材质中。这里使用了DuplicateObject函数来创建节点的副本,并将副本添加到材质的表达式列表中。在复制节点的过程中,函数会记录材质函数的输入节点和输出节点,分别存储在ExterInputExpressionArrExterOutPutExpressionArr数组中。

3、重新连接节点之间的输入和输出

在复制节点的过程中,节点之间的连接关系丢失了。因此,函数会遍历复制后的表达式节点,并根据原始节点之间的连接关系,重新连接复制后的节点。这里使用了ExpressionMap来存储原始节点和复制后节点之间的映射关系,以便快速找到对应的节点进行连接。

4、处理材质函数节点的输出连接

函数会遍历材质的所有表达式节点,找到连接到材质函数输出的节点,并将其存储在FuncExpressionOutputMap中。这里需要注意的是,一个材质函数的输出可能连接到多个节点,因此需要使用数组来存储连接的节点。

5、对材质函数的输出节点进行排序

为了保证展开后的节点顺序与原始材质函数中的顺序一致,函数会对材质函数的输出节点进行排序。排序的规则是先按照SortPriority属性排序,再按照节点名称的字母顺序排序。这里使用了一个自定义的排序函数SortMaterialFunc来实现排序逻辑。

6、修改材质属性的连接

在展开材质函数后,原本连接到材质函数输出的节点需要重新连接到展开后的输出节点。函数会遍历材质的所有属性,找到连接到材质函数输出的属性,并将其重新连接到对应的展开后的输出节点。

7、处理材质函数节点的输入连接

类似于输出连接的处理,函数会遍历材质函数节点的输入,并将其存储在FuncExpressionInputMap中。然后,对材质函数的输入节点进行排序,并重新连接输入节点到展开后的输入节点。

8、清理和更新材质

最后,函数会从材质的表达式列表中移除原始的材质函数节点,并标记材质包为脏,触发材质的编辑前后事件。这样,展开后的材质就可以在材质编辑器中正确显示和编辑了。

涉及

ExpandingMaterialFunction函数中:

  1. UMaterialExpressionMaterialFunctionCall:表示材质函数调用节点,用于在材质中调用材质函数。
  2. UMaterialExpressionFunctionInputUMaterialExpressionFunctionOutput:表示材质函数的输入节点和输出节点。
  3. UMaterialExpressionNamedRerouteDeclarationUMaterialExpressionNamedRerouteUsage:表示命名重路由节点的声明和使用,用于在材质函数中重用表达式。
  4. FExpressionInput:表示材质表达式节点的输入,包含了连接的表达式和输出索引等信息。
  5. EMaterialProperty:表示材质的属性枚举,如基础颜色、金属度、粗糙度等。

具体函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
void UEdLibrary::ExpandingMaterialFunction(UMaterial* Material, const FString FuncName, const int32 OffsetX, const int32 OffsetY)
{
if (!Material || FuncName.IsEmpty())
return;

TArray<UMaterialFunctionInterface*> MaterialFunctionArray;
TArray<UMaterialExpression*> MaterialExpressionFuncArr;
TArray<int32> EditYArray;
TArray<int32> EditXArray;

for (UMaterialExpression* Expression : Material->Expressions)
{
const UMaterialExpressionMaterialFunctionCall* FunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>(Expression);
if (FunctionCall && FunctionCall->MaterialFunction && FunctionCall->MaterialFunction->GetName() == FuncName)
{
EditYArray.Add(FunctionCall->MaterialExpressionEditorY);
EditXArray.Add(FunctionCall->MaterialExpressionEditorX);
MaterialFunctionArray.Add(FunctionCall->MaterialFunction);
MaterialExpressionFuncArr.Add(Expression);
}
}

if (MaterialFunctionArray.Num() == 0)
{
UE_LOG(AurogonEd1, Error, TEXT("ExpandingMaterialFunction : Not Find Material Function"));
return;
}

TMap<UMaterialExpression*, TArray<UMaterialExpression*>> ExterInputExpressionMap;
TMap<UMaterialExpression*, TArray<UMaterialExpression*>> ExterOutputExpressionMap;

TArray<int32> ExterOutPutIndexArr;

for (int i = 0; i < MaterialFunctionArray.Num(); ++i)
{
const UMaterialFunctionInterface* MaterialFunction = MaterialFunctionArray[i];
const TArray<UMaterialExpression*> *Expressions = MaterialFunction->GetFunctionExpressions();
TMap<UMaterialExpression*, UMaterialExpression*> ExpressionMap;

int32 AnchorEditorX = 0;
int32 AnchorEditorY = 0;

TArray<UMaterialExpression*> ExterInputExpressionArr;
TArray<UMaterialExpression*> ExterOutPutExpressionArr;
TMap<UMaterialExpressionNamedRerouteDeclaration*, UMaterialExpressionNamedRerouteDeclaration*> DeclarationMap;

for (UMaterialExpression* Expression : *Expressions)
{
UMaterialExpression* CopiedExpression = DuplicateObject<UMaterialExpression>(Expression, Material);
FString ClassName = Expression->GetClass()->GetName();

for (int32 InputIndex = 0; InputIndex < CopiedExpression->GetInputs().Num(); ++InputIndex)
{
if (ClassName == TEXT("MaterialExpressionFunctionInput"))
ExterInputExpressionArr.Add(CopiedExpression);
else if (ClassName == TEXT("MaterialExpressionFunctionOutput"))
ExterOutPutExpressionArr.Add(CopiedExpression);
}
{
if (AnchorEditorX == 0) AnchorEditorX = Expression->MaterialExpressionEditorX;
if (AnchorEditorY == 0) AnchorEditorY = Expression->MaterialExpressionEditorY;

CopiedExpression->MaterialExpressionEditorX = Expression->MaterialExpressionEditorX - AnchorEditorX + EditXArray[i] - OffsetX;
CopiedExpression->MaterialExpressionEditorY = Expression->MaterialExpressionEditorY - AnchorEditorY + EditYArray[i] + OffsetY;
CopiedExpression->MaterialExpressionGuid = Expression->MaterialExpressionGuid;
CopiedExpression->Material = Material;
CopiedExpression->Function = nullptr;

if (ClassName == TEXT("MaterialExpressionNamedRerouteDeclaration"))
{
UMaterialExpressionNamedRerouteDeclaration* FuncDeclaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(Expression);
UMaterialExpressionNamedRerouteDeclaration* Declaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(CopiedExpression);
DeclarationMap.Add(FuncDeclaration ,Declaration);
}
else if (ClassName == TEXT("MaterialExpressionNamedRerouteUsage"))
{
UMaterialExpressionNamedRerouteUsage* Usage = Cast<UMaterialExpressionNamedRerouteUsage>(CopiedExpression);
if (DeclarationMap.Contains(Usage->Declaration))
Usage->Declaration = DeclarationMap[Usage->Declaration];
}

ExpressionMap.Add(Expression, CopiedExpression);
Material->Expressions.Add(CopiedExpression);
}
}
ExterInputExpressionMap.Add(MaterialExpressionFuncArr[i] ,ExterInputExpressionArr);
ExterOutputExpressionMap.Add(MaterialExpressionFuncArr[i] ,ExterOutPutExpressionArr);

for (UMaterialExpression* MaterialExpression : *Expressions)
{
UMaterialExpression* CopiedExpression = ExpressionMap[MaterialExpression];
const TArray<FExpressionInput*> Inputs = MaterialExpression->GetInputs();
for (int32 InputIndex = 0; InputIndex < Inputs.Num(); ++InputIndex)
{
const FExpressionInput* Input = MaterialExpression->GetInput(InputIndex);
if (Input && Input->Expression)
{
UMaterialExpression* InputExpression = ExpressionMap[Input->Expression];
if (InputExpression)
{
FExpressionInput* CopiedInput = CopiedExpression->GetInput(InputIndex);
CopiedInput->Expression = InputExpression;
CopiedInput->OutputIndex = Input->OutputIndex;
}
}
}
}
}

TArray<int8> ParentExpressionInputIndexArr;
TArray<int8> ExpressionInputIndexArr;
TMap<UMaterialExpression*, TArray<UMaterialExpression*>> FuncExpressionOutputMap;

for (UMaterialExpression* Expression : Material->Expressions)
{
for (int8 j = 0; j < MaterialExpressionFuncArr.Num(); j++)
{
for (int8 i = 0; i < Expression->GetInputs().Num(); i++)
{
if (Expression->GetInput(i)->Expression == MaterialExpressionFuncArr[j])
{
UMaterialExpression* FuncExpression = MaterialExpressionFuncArr[j];
if (FuncExpressionOutputMap.Contains(FuncExpression))
{
FuncExpressionOutputMap[FuncExpression].Add(Expression);
}
else
{
TArray<UMaterialExpression*> ParentInputExpressionArr;
ParentInputExpressionArr.Add(Expression);
FuncExpressionOutputMap.Add(FuncExpression, ParentInputExpressionArr);
}
ParentExpressionInputIndexArr.Add(i);
ExpressionInputIndexArr.Add(j);
ExterOutPutIndexArr.Add(Expression->GetInput(i)->OutputIndex);
}
}
}
}

TArray<FExpressionInput*> MaterialExpressInputArr;

for (int i = 0; i < EMaterialProperty::MP_MAX; i++)
{
FExpressionInput* ExpressionInput = Material->GetExpressionInputForProperty(static_cast<EMaterialProperty>(i));
if (!ExpressionInput) continue;
UMaterialExpression* MaterialExpression = ExpressionInput->Expression;
if (!IsValid(MaterialExpression)) continue;
if (MaterialExpressionFuncArr.Contains(MaterialExpression))
MaterialExpressInputArr.Add(ExpressionInput);
}

auto SortMaterialFunc = [](const UMaterialExpression& A,const UMaterialExpression& B)
{
int32 ASortPriority = 0;
int32 BSortPriority = 0;
if (Cast<UMaterialExpressionFunctionInput>(&A))
{
ASortPriority = Cast<UMaterialExpressionFunctionInput>(&A)->SortPriority;
BSortPriority = Cast<UMaterialExpressionFunctionInput>(&B)->SortPriority;
}
else if (Cast<UMaterialExpressionFunctionOutput>(&A))
{
ASortPriority = Cast<UMaterialExpressionFunctionOutput>(&A)->SortPriority;
BSortPriority = Cast<UMaterialExpressionFunctionOutput>(&B)->SortPriority;
}

if (ASortPriority == BSortPriority)
{
int32 Index = 0;
FString Astr = A.GetInputName(0).ToString();
FString Bstr = B.GetInputName(0).ToString();
while (Index < Astr.Len() && Index < Bstr.Len())
{
const TCHAR Char1 = Astr[Index];
const TCHAR Char2 = Bstr[Index];
if (FChar::ToLower(Char1) < FChar::ToLower(Char2))
return true;
else if (FChar::ToLower(Char1) > FChar::ToLower(Char2))
return false;
++Index;
}
}
else
return ASortPriority < BSortPriority;
return false;
};
for (int8 j = 0; j < MaterialExpressionFuncArr.Num(); j++)
{
UMaterialExpression* ExpressionFunc = MaterialExpressionFuncArr[j];
auto ExterOutPutExpressionArr = ExterOutputExpressionMap[ExpressionFunc];

ExterOutPutExpressionArr.Sort(SortMaterialFunc);

for (auto Input : MaterialExpressInputArr)
{
UMaterialExpressionMaterialFunctionCall* MaterialExpressionMaterialFunctionCall = Cast<UMaterialExpressionMaterialFunctionCall>(ExpressionFunc);
auto Outputs = MaterialExpressionMaterialFunctionCall->FunctionOutputs;
FName MaterialOutPutName = Outputs[Input->OutputIndex].ExpressionOutput->OutputName;

for (auto OutPutExpression : ExterOutPutExpressionArr)
{
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(OutPutExpression);
FName OutPutName = FunctionOutput->OutputName;;
if (MaterialOutPutName == OutPutName)
Input->Expression = OutPutExpression;
}
}

if (!FuncExpressionOutputMap.Contains(ExpressionFunc))
continue;

auto ParentInputExpressionArr = FuncExpressionOutputMap[ExpressionFunc];
for (int8 i = 0; i < ParentInputExpressionArr.Num(); i++)
{
UMaterialExpression* Expression = ParentInputExpressionArr[i];
FExpressionInput* Input = Expression->GetInput(ParentExpressionInputIndexArr[i]);
if (Input->Expression)
{
int8 Index = ExpressionInputIndexArr[i];
int8 CIndex = ExterOutPutIndexArr[j + i];
Input->Expression = ExterOutPutExpressionArr[CIndex];
Input->OutputIndex = ExterOutPutIndexArr[Index];
}
}
}

TArray<int8> ParentExpressionOutputIndexArr;
TMap<UMaterialExpression*, TArray<FExpressionInput*>> FuncExpressionInputMap;

for (int8 j = 0; j < MaterialExpressionFuncArr.Num(); ++j)
{
UMaterialExpression* Expression = MaterialExpressionFuncArr[j];
TArray<FExpressionInput*> ParentOutputExpressionArr;

for (int8 i = 0; i < Expression->GetInputs().Num(); i++)
{
FExpressionInput* Input = Expression->GetInput(i);
ParentOutputExpressionArr.Add(Input);
}
FuncExpressionInputMap.Add(Expression, ParentOutputExpressionArr);
}

for (int8 j = 0; j < MaterialExpressionFuncArr.Num(); ++j)
{
UMaterialExpression* Expression = MaterialExpressionFuncArr[j];
if (!FuncExpressionInputMap.Contains(Expression))
continue;

auto ParentOutputExpressionArr = FuncExpressionInputMap[Expression];
auto ExpressionArr = ExterInputExpressionMap[Expression];
ExpressionArr.Sort(SortMaterialFunc);

for (int8 i = 0; i < ParentOutputExpressionArr.Num(); i++)
{
FExpressionInput* Input = ExpressionArr[i]->GetInput(0);
FExpressionInput* OutPutExpression = ParentOutputExpressionArr[i];
if (Input && OutPutExpression && OutPutExpression->Expression)
{
Input->Expression = OutPutExpression->Expression;
Input->OutputIndex = OutPutExpression->OutputIndex;

Input->Mask = OutPutExpression->Mask;
Input->MaskR = OutPutExpression->MaskR;
Input->MaskG = OutPutExpression->MaskG;
Input->MaskB = OutPutExpression->MaskB;
Input->MaskA = OutPutExpression->MaskA;
}
}
}
for (UMaterialExpression* Expression : MaterialExpressionFuncArr)
{
Material->Expressions.Remove(Expression);
}
Material->MarkPackageDirty();
Material->PreEditChange(nullptr);
Material->PostEditChange();
}

UE4:在编辑器下对材质函数展开的方法
http://muchenhen.com/posts/55800/
作者
木尘痕
发布于
2024年5月28日
许可协议