需求背景
TA使用了大量的MaterialFunction,这次是需要在已经封装调用了MaterialFunction的地方当场临时展开方便查看一些问题,想要一个编辑器接口。
本次功能实现由小伙伴友情提供 ,这里学习一个。
函数概述
实现了一个ExpandingMaterialFunction
函数,其主要功能是将材质中的指定材质函数展开,将材质函数的内部节点复制到材质中,并重新连接输入和输出。函数的签名如下:
1 void UEdLibrary::ExpandingMaterialFunction (UMaterial* Material, const FString FuncName, const int32 OffsetX, const int32 OffsetY)
参数说明:
Material
:要进行展开操作的材质对象。
FuncName
:要展开的材质函数的名称。
OffsetX
和OffsetY
:展开后的节点在材质编辑器中的位置偏移量。
函数实现步骤
1、查找材质函数节点
函数首先遍历材质的所有表达式节点,找到名称与给定的FuncName
匹配的材质函数节点。这里使用了UMaterialExpressionMaterialFunctionCall
类来判断节点是否为材质函数调用节点。找到的材质函数节点会被存储在MaterialFunctionArray
和MaterialExpressionFuncArr
数组中,同时记录节点在材质编辑器中的位置。
2、复制材质函数的内部节点
对于每个找到的材质函数节点,函数会复制其内部的所有表达式节点到材质中。这里使用了DuplicateObject
函数来创建节点的副本,并将副本添加到材质的表达式列表中。在复制节点的过程中,函数会记录材质函数的输入节点和输出节点,分别存储在ExterInputExpressionArr
和ExterOutPutExpressionArr
数组中。
3、重新连接节点之间的输入和输出
在复制节点的过程中,节点之间的连接关系丢失了。因此,函数会遍历复制后的表达式节点,并根据原始节点之间的连接关系,重新连接复制后的节点。这里使用了ExpressionMap
来存储原始节点和复制后节点之间的映射关系,以便快速找到对应的节点进行连接。
4、处理材质函数节点的输出连接
函数会遍历材质的所有表达式节点,找到连接到材质函数输出的节点,并将其存储在FuncExpressionOutputMap
中。这里需要注意的是,一个材质函数的输出可能连接到多个节点,因此需要使用数组来存储连接的节点。
5、对材质函数的输出节点进行排序
为了保证展开后的节点顺序与原始材质函数中的顺序一致,函数会对材质函数的输出节点进行排序。排序的规则是先按照SortPriority
属性排序,再按照节点名称的字母顺序排序。这里使用了一个自定义的排序函数SortMaterialFunc
来实现排序逻辑。
6、修改材质属性的连接
在展开材质函数后,原本连接到材质函数输出的节点需要重新连接到展开后的输出节点。函数会遍历材质的所有属性,找到连接到材质函数输出的属性,并将其重新连接到对应的展开后的输出节点。
7、处理材质函数节点的输入连接
类似于输出连接的处理,函数会遍历材质函数节点的输入,并将其存储在FuncExpressionInputMap
中。然后,对材质函数的输入节点进行排序,并重新连接输入节点到展开后的输入节点。
8、清理和更新材质
最后,函数会从材质的表达式列表中移除原始的材质函数节点,并标记材质包为脏,触发材质的编辑前后事件。这样,展开后的材质就可以在材质编辑器中正确显示和编辑了。
涉及
在ExpandingMaterialFunction
函数中:
UMaterialExpressionMaterialFunctionCall
:表示材质函数调用节点,用于在材质中调用材质函数。
UMaterialExpressionFunctionInput
和UMaterialExpressionFunctionOutput
:表示材质函数的输入节点和输出节点。
UMaterialExpressionNamedRerouteDeclaration
和UMaterialExpressionNamedRerouteUsage
:表示命名重路由节点的声明和使用,用于在材质函数中重用表达式。
FExpressionInput
:表示材质表达式节点的输入,包含了连接的表达式和输出索引等信息。
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 (); }