【Blender】通过 Python 脚本为对象应用材质(大总结)
前置知识:如何添加材质
添加材质的步骤
具体可以分解为以 5 步
- 创建新材质
- 制作可用的节点
- 添加对象
- 设置节点的值
- 将材质应用在物体上
Principled BSDF 节点的内容
节点的每一项参数都是从 0 开始分配的一个编号(Index
)。
以 Principled BSDF
为例,让我们来看看该如何使用它。
如上图所示,主要使用的参数项所对应的编号如下
节点参数 | 参数编号 |
---|---|
Base Color | 0 |
Subsurface | 1 |
... | |
Metallic | 4 |
... | |
Roughness | 7 |
... | |
Transmission | 15 |
然后我们就可以使用该节点的 nodes["Principled BSDF"].inputs[index].default_value
来为它们指定具体的值。
用 Python 制作玻璃材质的示例
例子1:创建一个简单的玻璃球(ico_sphere)
完整代码与注释
import bpy
# 制作新的材质
material_glass = bpy.data.materials.new('Green')
# 开启使用节点
material_glass.use_nodes = True
# 添加对象(ico_sphere)
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
# 设定节点的值
# 创建变量 p_BSDF ,减少重复输入以下长串的名称
p_BSDF = material_glass.node_tree.nodes["Principled BSDF"]
# 可以通过索引,也可以通过名字访问
# 0→BaseColor / 7→roughness(=粗糙度) / 15→transmission(=透光性)
#default_value = (R, G, B, A)
p_BSDF.inputs[0].default_value = (0, 1, 0, 1)
p_BSDF.inputs[7].default_value = 0
p_BSDF.inputs[15].default_value = 1
# 给对象添加材质元素
bpy.context.object.data.materials.append(material_glass)
例子2:在三维空间中排列玻璃立方体的程序
完整代码与注释
import bpy
for i in range(0,5):
for j in range(0,5):
for k in range(0,5):
# 制作新材质
material_glass = bpy.data.materials.new('Red')
# 开启使用节点
material_glass.use_nodes = True
bpy.ops.mesh.primitive_cube_add(
size=0.8,
# 分别决定立方体在 x、y、z 上的坐标
location=(i, j, k),
)
p_BSDF = material_glass.node_tree.nodes["Principled BSDF"]
p_BSDF.inputs[0].default_value = (1, 0, 0, 1)
p_BSDF.inputs[7].default_value = 0
p_BSDF.inputs[15].default_value = 1
bpy.context.object.data.materials.append(material_glass)
不过以上代码会 每个立方体都创建一个材质
如果只想让它们 共享 一个材质的话,可以将 材质创建 部分的代码移到循环块之外,即修改为如下
import bpy
# 制作新材质
material_glass = bpy.data.materials.new('Red')
# 开启使用节点
material_glass.use_nodes = True
nodes = material_glass.node_tree.nodes
p_BSDF = nodes.get("Principled BSDF") # 获取指定的节点
for i in range(0,5):
for j in range(0,5):
for k in range(0,5):
bpy.ops.mesh.primitive_cube_add(
size=0.8,
# 分别决定立方体在 x、y、z 上的坐标
location=(i, j, k),
)
p_BSDF.inputs[0].default_value = (1, 0, 0, 1)
p_BSDF.inputs[7].default_value = 0
p_BSDF.inputs[15].default_value = 1
bpy.context.object.data.materials.append(material_glass)
注意:添加了立方体之后还可以接着对它进行 缩放、旋转、平移 等的变换
bpy.ops.transform.resize(value=(2.0,1.0,0.1)) bpy.ops.transform.rotate(value=3.1415/6, orient_axis='Y') bpy.ops.transform.translate(value=(0,0,5))
Bonus
为材质创建驱动表达式
示例
# 插入驱动表达式到粗糙度
driver = diffuse.inputs[1].driver_add("default_value")
var = driver.driver.variables.new()
var.name = "variable"
var.targets[0].data_path = "PATH"
var.targets[0].id = "Target_Object_Name"
driver.driver.expression = "variable"
# 移除驱动表达式到粗糙度
diffuse.inputs[1].driver_remove("default_value")
为材质打关键帧
示例
# 为第 10 帧的粗糙度添加关键帧
diffuse.inputs[1].keyframe_insert("default_value", frame=10)
创建材质节点
所有可以创建的 节点类型:
示例
# 移除特定节点
nodes.remove(diffuse)
...
# 清除所有节点以开始清理
nodes.clear()
...
# 创建自发光节点
node_emission = nodes.new(type='ShaderNodeEmission')
node_emission.inputs[0].default_value = (0,1,0,1) # green RGBA
node_emission.inputs[1].default_value = 5.0 # strength
node_emission.location = 0,0
...
# 创建输出节点
node_output = nodes.new(type='ShaderNodeOutputMaterial')
node_output.location = 400,0
材质节点的链接与断开
示例
links = mat.node_tree.links
# 链接自发光节点 -> 输出节点
link = links.new(node_emission.outputs[0], node_output.inputs[0])
...
# 指定的接口
from_s = node_emission.outputs[0]
to_s = node_output.inputs[0]
# 遍历得到指定的链接,然后得到其下一个链接
link = next((l for l in links if l.from_socket == from_s and l.to_socket == to_s), None) # 默认值为 None
# 断开指定的链接
links.remove(link)
注
next(): 返回迭代器的下一个项目。
next()
函数要和生成迭代器的iter()
函数一起使用。
为材质的纹理节点赋图像
注意:确保在上下文中已经为对象分配了一个材质,并且图像文件路径是正确的
加载本地图像
示例 1
import bpy
# 在上下文中获取物体的激活的材质
mat = bpy.context.object.active_material
# 获取该材质的节点树节点
nodes = mat.node_tree.nodes
nodes.clear()
# 添加 Principled Shader 节点
node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
node_principled.location = 0,0
# 添加 Image Texture 节点
node_tex = nodes.new('ShaderNodeTexImage')
# 从本地加载图像并赋给纹理节点
node_tex.image = bpy.data.images.load("//your_image.exr")
node_tex.location = -400,0
# 添加 Output 节点
node_output = nodes.new(type='ShaderNodeOutputMaterial')
node_output.location = 400,0
# 链接相应节点
links = mat.node_tree.links
link = links.new(node_tex.outputs["Color"], node_principled.inputs["Base Color"])
link = links.new(node_principled.outputs["BSDF"], node_output.inputs["Surface"])
示例 2
import bpy
import os
C = bpy.context
scn = C.scene
# 获取 .blend 文件所在路径下的目录,并寻找 *.exr 图像
folder = bpy.path.abspath("//images")
images = [os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".exr")]
# 获取材质及其节点树
mat = C.object.active_material
nodes = mat.node_tree.nodes
# 获取指定节点
img_node = nodes.get("Image Texture")
if img_node:
# 迭代所有上一步寻找到的图像,并渲染为 *.png 保存为本地
for img in images:
img_node.image = bpy.data.images.load(img)
# 设置输出路径并渲染
img_folder, img_file = os.path.split(img)
img_name, img_ext = os.path.splitext(img_file)
# 以下 `png` 只是一个占位符,具体保存什么图象格式取决于 blender 中设置的图像格式
scn.render.filepath = bpy.path.abspath("//rndr_{}.png".format(img_name))
bpy.ops.render.render(write_still=True)
获取已存在图像
如果图像已经加载或已经打包进 Blend 文件,则代码可以简化如下
...
# 添加 Image Texture 节点
node_tex = nodes.new('ShaderNodeTexImage')
node_tex.location = -400,0
# 根据名字获取图像并赋给纹理节点 Node.image
img = bpy.data.images.get("Image Name")
if img:
node_tex.image = img
...
添加文字并为其关联材质
添加的文字
import bpy
# 添加文本,以编辑模式进行编辑
bpy.ops.object.text_add(enter_editmode = True,location = (0,0,0))
# 删除初始文本
bpy.ops.font.delete(type = 'PREVIOUS_WORD')
# 输入文本
bpy.ops.font.text_insert(text = 'Blender')
# 结束编辑模式
bpy.ops.object.editmode_toggle()
为文字关联材质
结合前文的知识与代码,我们可以写出如下代码
import bpy
# 创建材质节点
material_glass = bpy.data.materials.new('Blue')
material_glass.use_nodes = True
# 添加文字
bpy.ops.object.text_add(enter_editmode = True,location = (0,0,0))
bpy.ops.font.delete(type = 'PREVIOUS_WORD')
bpy.ops.font.text_insert(text = 'Blender')
bpy.ops.object.editmode_toggle()
# 操作材质节点
p_BSDF = material_glass.node_tree.nodes["Principled BSDF"]
p_BSDF.inputs[4].default_value = 1
p_BSDF.inputs[7].default_value = 0
p_BSDF.inputs[0].default_value = (0, 0, 1, 1)
bpy.context.object.data.materials.append(material_glass)
清理场景中的网格
挺方便的工具代码,可以放在脚本的开头执行,这样就不用每次都手动清除之前创建的网格
# 已存在的网格,通通都删除
for item in bpy.data.meshes:
bpy.data.meshes.remove(item)
如果想 删除指定 的网格,可以修改如下代码
# 移除名为 "Cube" 的网格
if "Cube" in bpy.data.meshes:
mesh = bpy.data.meshes["Cube"]
print("removing mesh", mesh)
bpy.data.meshes.remove(mesh)
转载自:https://juejin.cn/post/7167268956770664456