除了之前介绍过的通过Python来创建User Defined Enum,Struct、DataTable以外,用Python来处理材质、材质函数也是一个很常见的一个需求。
UE5(5.0.3)引擎自带的MaterialEditingLibrary可以完成不少对材质的操作,但也有不少操作是通过它完成不了的。
比如:
-
将材质属性连接到World Position Offset等属性
因为MP_WorldPositionOffset 这个枚举项在cpp中被标记为Hidden,因此它无法在Python中使用
-
为Get/SetMaterialAttributes 添加输出/输入引脚等
我们可以理解UE对部分功能的隐藏和封装,这能避免不少麻烦。比如我们愚蠢地在5.0.3中将通过脚本将ShadingModel设置为MSM_Strata (DisplayName="Strata", Hidden), 这马上就会导致编辑器的崩溃。但隐藏那些用户可以通过界面进行操作的那些选项和枚举,无疑给自动化操作带来了额外的工作量,这就是TAPython 要存在的意义。
通过MaterialEditingLibrary和TAPython的[PythonMaterialLib][3],我们已经可以将绝大部分对材质的操作Python脚本化。
创建¶
创建材质¶
下面这段代码,将在"/Game/CreatedByPython"这个位置创建一个名为M_CreatedByPython的材质。
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
my_mat = asset_tools.create_asset("M_CreatedByPython", "/Game/CreatedByPython", unreal.Material, unreal.MaterialFactoryNew())
unreal.EditorAssetLibrary.save_asset(my_mat.get_path_name())
创建材质函数¶
创建材质函数的方法与创建材质类似
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
my_mf = asset_tools.create_asset("MF_CreatedByPython", "/Game/CreatedByPython", unreal.MaterialFunction, unreal.MaterialFunctionFactoryNew())
添加材质节点¶
- 添加一个普通的节点
在my_mat添加一个 加法节点,并将其赋值给变量node_add
注意,本文介绍的方法或者说函数,绝大部分都不会触发材质编辑器UI的刷新。因此,如果你用材质编辑器打开了当前操作的材质,那么你需要关闭窗口,然后重新打开才能看到新添加的节点。
node_add = unreal.MaterialEditingLibrary.create_material_expression(my_mat, unreal.MaterialExpressionAdd, node_pos_x=-200, node_pos_y=0)
在材质函数中添加材质节点的做法也很类似,只不过调用的方法是unreal.MaterialEditingLibrary.create_material_expression_in_function,其他类似的功能也都有材质函数所用的版本,在实际使用的时候替换成对于的版本即可。
- 添加一个贴图采样节点
添加TextureSample,并通过set_editor_property 来设置它所使用的贴图:
node_tex = unreal.MaterialEditingLibrary.create_material_expression(my_mat, unreal.MaterialExpressionTextureSampleParameter2D
, node_pos_x=-600, node_pos_y=0)
texture_asset = unreal.load_asset("/Game/StarterContent/Textures/T_Brick_Clay_Beveled_D")
node_tex.set_editor_property("texture", texture_asset)
- 添加一个材质函数节点
材质函数节点的类型是:MaterialExpressionMaterialFunctionCall,然后我们也同样需要通过set_editor_property,设置它实际使用的MF。
我们熟悉的break节点,其实也是一个材质函数
node_break = unreal.MaterialEditingLibrary.create_material_expression(my_mat, unreal.MaterialExpressionMaterialFunctionCall, -600, 300)
node_break.set_editor_property("material_function", unreal.load_asset("/Engine/Functions/Engine_MaterialFunctions02/Utility/BreakOutFloat2Components"))
以上部分都是用Unreal引擎自带的MaterialEditingLibrary来完成。下面涉及到的部分内容,有的就需要TAPython中的PythonMaterialLib来加以实现
添加特殊材质节点¶
部分材质节点比较特殊,它们有用户可以添加的输入和输出引脚。输入和输出引脚的内容以FGuid的形式保存在Property中。如果简单地设置其中的内容,并不会引起引脚数量的添加,并且引发报错。
因此,需要使用到 PythonMaterialLib中的add_input_at_expression_set_material_attributes方法,为其添加输入/输出引脚:
node_sma = unreal.MaterialEditingLibrary.create_material_expression(my_mat, unreal.MaterialExpressionSetMaterialAttributes, node_pos_x=500, node_pos_y=0)
property_names = ["MP_Specular", "MP_Normal", "MP_WorldPositionOffset", "MP_CustomData0", "MP_CustomizedUVs0"]
for mp_name in property_names:
unreal.PythonMaterialLib.add_input_at_expression_set_material_attributes(node_sma, mp_name)
# same with MaterialExpressionGetMaterialAttributes
node_gma = unreal.MaterialEditingLibrary.create_material_expression(my_mat, unreal.MaterialExpressionGetMaterialAttributes, node_pos_x=200, node_pos_y=0)
for mp_name in property_names:
unreal.PythonMaterialLib.add_output_at_expression_get_material_attributes(node_gma, mp_name)
连接材质节点¶
- 将节点的输出引脚连接到材质的某个属性
unreal.MaterialEditingLibrary.connect_material_property(from_expression=node_add
, from_output_name=""
, property_=unreal.MaterialProperty.MP_BASE_COLOR)
- 将节点的输出引脚连接到WorldPositionOffset 等“隐藏”属性
由于unreal.MaterialProperty中并没有包含 MP_WorldPositionOffset 等在cpp中标记为hidden的枚举项,因此需要用到PythonMaterialLib中扩展的同名函数。该函数的第三个参数类型改为了字符串类型
unreal.PythonMaterialLib.connect_material_property(from_expression=node_add
, from_output_name=""
, material_property_str="MP_WorldPositionOffset")
注意:当输入或者输出节点名:from_output_name、to_input_name为空字符串时,会默认使用第一个输入或者输出引脚
如果节点有多个输出,想要查看具体节点有那些output_name 可以使用这个方法:[GetMaterialExpressionOutputNames][#_断开节点]
- 将两个材质节点之间的属性进行连接
unreal.MaterialEditingLibrary.connect_material_expressions(from_expression=node_tex, from_output_name="", to_expression=node_add, to_input_name="A")
删除节点¶
unreal.MaterialEditingLibrary.delete_material_expression(my_mat, node_need_be_delete)
断开节点¶
- 断开节点之间的连接
一个材质节点的输出节点可以连接到多个其他节点,但一个材质节点的输入节点是唯一的。因此,我们要断开节点间的连接的话,只需要指定被连接到节点和它的输入名:
unreal.PythonMaterialLib.disconnect_expression(node_add, "A")
- 断开材质节点到母材质的连接
unreal.PythonMaterialLib.disconnect_material_property(my_mat, material_property_str="MP_BaseColor")
布局材质节点¶
除了在创建材质节点时指定它的位置以外,可可以在材质连接之后,对其应用自动布局:
- 自动布局
unreal.MaterialEditingLibrary.layout_material_expressions(my_mat)
- 创建时指定节点位置
unreal.MaterialEditingLibrary=create_material_expression( material, expression_class, node_pos_x=x, node_pos_y=y):
查询¶
获取材质节点实例¶
我们可以在材质编辑中添加快捷菜单,迅速获取当前选中的节点,具体的添加方法可见1.0.8的更新说明。
"OnMaterialEditorMenu": {
"name": "Python Menu On Material Editor",
"items":
[
{
"name": "TA Python Material Example",
"items": [
{
"name": "Print Editing Material / MF",
"command": "print(%asset_paths)"
},
{
"name": "Log Editing Nodes",
"command": "editing_asset = unreal.load_asset(%asset_paths[0]); unreal.PythonMaterialLib.log_editing_nodes(editing_asset)"
},
{
"name": "Selected Nodes --> global variable _r",
"command": "_r = unreal.PythonMaterialLib.get_selected_nodes_in_material_editor(unreal.load_asset(%asset_paths[0]))"
},
{
"name": "Selected Node --> _r",
"command": "_r = unreal.PythonMaterialLib.get_selected_nodes_in_material_editor(unreal.load_asset(%asset_paths[0]))[0]"
}
]
}
]
},
比如下图中的 "Log Editing Nodes" 可以输出选中的节点的简要信息
"Selected Nodes --> global variable _r" 可以将当前选中的材质节点赋值个全局变量 "_r",然后可以在Python console中继续对其进行操作,当然也可以通过ObjectDetailViewer查看该节点的各个Property和数值
获取材质节点属性¶
- 获取材质节点输入名列表
unreal.PythonMaterialLib.get_material_expression_input_names(some_node)
- 获取材质节点输出名列表
unreal.PythonMaterialLib.get_material_expression_output_names(some_node)
- 获取材质节点上的标题列表
unreal.PythonMaterialLib.get_material_expression_captions(some_node)
- 输出材质节点的简要信息
unreal.PythonMaterialLib.log_material_expression(some_node)
输出连接关系¶
- 以树状形式输出材质中的节点连接关系
unreal.PythonMaterialLib.log_mat(my_mat)
同样对于材质函数,也有类似的功能
unreal.PythonMaterialLib.log_mf(my_mf)
获取材质、材质函数中的节点及其连接关系¶
- 获取当前材质中的所有节点
all_expressions = unreal.PythonMaterialLib.get_material_expressions(my_mat)
- 获取材质函数中的所有节点
all_expressions_in_mf = unreal.PythonMaterialLib.get_material_function_expressions(my_mf)
- 获材质中的个节点的连接关系
下面例子将以数组的形式返回材质中所有连接,连接的类型为TAPythonMaterialConnection
for connection in unreal.PythonMaterialLib.get_material_connections(my_mat)
print(connection)
TAPythonMaterialConnection(StructBase):
class TAPythonMaterialConnection(StructBase):
r"""
TAPython Material Connection
**C++ Source:**
- **Plugin**: TAPython
- **Module**: TAPython
- **File**: PythonMaterialLib.h
**Editor Properties:** (see get_editor_property/set_editor_property)
- ``left_expression_index`` (int32): [Read-Write] Left Expression Index:
The index of material expression in source material's expressions, which the connection from
- ``left_output_index`` (int32): [Read-Write] Left Output Index:
The index of output in the expression
- ``left_output_name`` (str): [Read-Write] Left Output Name:
The name of output pin
- ``right_expression_index`` (int32): [Read-Write] Right Expression Index:
The index of material expression in source material's expressions, which the connection to
- ``right_expression_input_index`` (int32): [Read-Write] Right Expression Input Index:
The index of input in the expression
- ``right_expression_input_name`` (str): [Read-Write] Right Expression Input Name:
The name of input pin
其中的属性有:
- left_expression_index
- left_output_index
- left_output_name
- right_expression_index
- right_expression_input_index
- right_expression_input_name
其中"left_expression_index", "right_expression_index" 分别为节点连线左侧和右侧的节点。
其他属性还包含,连接的引脚序号和引脚名称等。我们可以更具实际需要选择使用引脚索引或者引脚名,(比如不同的项目间,引脚的顺序不同,或者修改了引脚名)
导出材质信息¶
- 以JSON的形式获取整个材质的连接信息
这个功能与将材质以T3D或者COPY的形式导出的功能实际上是一样的,只不过使用了更为通用的JSON格式。通过这个方法,我们可以掌握整个材质中的节点信息,进而对其进行分析、优化和其他操作:
# export the material content to a JSON file
content_in_json = unreal.PythonMaterialLib.get_material_content(my_mat)
with open("file_path_of_json_file", 'w') as f:
f.write(content_in_json)
获取材质的HLSL代码¶
下面代码将打印出当前材质在SM5下的hlsl代码
print(unreal.PythonMaterialLib.get_hlsl_code(my_mat))
- 获取材质的shadermap信息
包括材质变体,shaderCode大小,贴图使用数量等都会包含在输出信息中
unreal.PythonMaterialLib.get_shader_map_info(_r, "PCD3D_ES3_1")
范例¶
可见TAPython_TestSamples中的材质部分(暂缺)
参考¶
- UE 官方的MaterialEditingLibrary
- 本文中涉及到的[PythonMaterialLib文档][3]
[3]: https://www.tacolor.xyz/pages/PythonEditorLib.html