likes
comments
collection
share

【Blender】通过 Python 脚本为对象应用材质(大总结)

作者站长头像
站长
· 阅读数 75

前置知识:如何添加材质

添加材质的步骤

具体可以分解为以 5 步

  • 创建新材质
  • 制作可用的节点
  • 添加对象
  • 设置节点的值
  • 将材质应用在物体上

Principled BSDF 节点的内容

节点的每一项参数都是从 0 开始分配的一个编号(Index)。 以 Principled BSDF 为例,让我们来看看该如何使用它。

【Blender】通过 Python 脚本为对象应用材质(大总结)

如上图所示,主要使用的参数项所对应的编号如下

节点参数参数编号
Base Color0
Subsurface1
...
Metallic4
...
Roughness7
...
Transmission15

然后我们就可以使用该节点的 nodes["Principled BSDF"].inputs[index].default_value 来为它们指定具体的值。

用 Python 制作玻璃材质的示例

例子1:创建一个简单的玻璃球(ico_sphere)

【Blender】通过 Python 脚本为对象应用材质(大总结)

完整代码与注释

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:在三维空间中排列玻璃立方体的程序

【Blender】通过 Python 脚本为对象应用材质(大总结)

完整代码与注释

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)

不过以上代码会 每个立方体都创建一个材质

【Blender】通过 Python 脚本为对象应用材质(大总结)

如果只想让它们 共享 一个材质的话,可以将 材质创建 部分的代码移到循环块之外,即修改为如下

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

...

添加文字并为其关联材质

添加的文字

【Blender】通过 Python 脚本为对象应用材质(大总结)

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()

为文字关联材质

【Blender】通过 Python 脚本为对象应用材质(大总结)

结合前文的知识与代码,我们可以写出如下代码

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
评论
请登录