协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
blender-compositing【开发与工程】Blender's compositor post-processes renders with a node graph, and this skill scripts it from Python (bpy) in Blender 5.x. Use when the user wants to set up compositor nodes, add post-processing effects, color correct renders, combine render passes, apply blur or glare, key green screens, write passes to multilayer EXR, or port a compositing script t…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Build node-based compositing pipelines in Blender using Python. Set up render passes, add post-processing effects (blur, glare, color correction), combine layers, key green screens, and output final composited images — all scriptable from the terminal. Blender 5.0 reworked this API: the compositing tree is its own data-block (scene.compositing_node_group), the final image goes to a Group Output node, and almost every node option became an input socket.
import bpy
scene = bpy.context.scene
tree = bpy.data.node_groups.new("Final Comp", "CompositorNodeTree")
scene.compositing_node_group = tree
nodes, links = tree.nodes, tree.links
# Minimum setup: Render Layers -> Group Output (its first input must be a Color socket)
rl = nodes.new("CompositorNodeRLayers")
out = nodes.new("NodeGroupOutput")
out.location = (900, 0)
tree.interface.new_socket(name="Image", in_out="OUTPUT", socket_type="NodeSocketColor")
links.new(rl.outputs["Image"], out.inputs["Image"])
# Optional viewer for the Image Editor backdrop
viewer = nodes.new("CompositorNodeViewer")
viewer.location = (900, -200)
links.new(rl.outputs["Image"], viewer.inputs["Image"])
A new tree is empty; do not rely on default nodes. To reuse an existing tree, read scene.compositing_node_group (it is None until one is assigned).
Options are sockets: node.inputs["Name"].default_value. Menu inputs take the label shown in the UI ("Bloom", "Fog Glow"), not the old upper-case identifiers.
# Brightness/Contrast
bc = nodes.new("CompositorNodeBrightContrast")
bc.inputs["Brightness"].default_value = 10
bc.inputs["Contrast"].default_value = 20
# Hue/Saturation/Value
hsv = nodes.new("CompositorNodeHueSat")
hsv.inputs["Hue"].default_value = 0.5 # 0.5 = no change
hsv.inputs["Saturation"].default_value = 1.2
# Color Balance: two inputs are named "Lift" (float and color), so use the identifier
cb = nodes.new("CompositorNodeColorBalance")
cb.inputs["Type"].default_value = "Lift/Gamma/Gain"
cb.inputs["Color Lift"].default_value = (0.95, 0.95, 1.0, 1.0) # cool shadows
cb.inputs["Color Gain"].default_value = (1.1, 1.05, 1.0, 1.0) # warm highlights
# Gamma and Math are the shader nodes now (CompositorNodeGamma was removed)
gamma = nodes.new("ShaderNodeGamma")
gamma.inputs["Gamma"].default_value = 1.2
# Blur: Size is a 2D vector in pixels
blur = nodes.new("CompositorNodeBlur")
blur.inputs["Type"].default_value = "Gaussian"
blur.inputs["Size"].default_value = (10, 10)
# Glare: Bloom, Ghosts, Streaks, Fog Glow, Simple Star, Sun Beams, Kernel
glare = nodes.new("CompositorNodeGlare")
glare.inputs["Type"].default_value = "Bloom"
glare.inputs["Quality"].default_value = "High"
glare.inputs["Threshold"].default_value = 0.8
glare.inputs["Size"].default_value = 0.6 # 0..1
# Denoise (best placed right after Render Layers)
denoise = nodes.new("CompositorNodeDenoise")
# Sharpen
sharpen = nodes.new("CompositorNodeFilter")
sharpen.inputs["Type"].default_value = "Box Sharpen"
To discover the inputs and menu values of any node:
for s in glare.inputs:
print(s.name, s.identifier, s.bl_idname, getattr(s, "default_value", None))
# Enable passes on the view layer, then read the output names from the node
view_layer = bpy.context.view_layer
view_layer.use_pass_diffuse_color = True
view_layer.use_pass_ambient_occlusion = True
view_layer.use_pass_z = True
print([o.name for o in rl.outputs if o.enabled])
# Cycles: ['Image', 'Alpha', 'Depth', 'Diffuse Color', 'Ambient Occlusion', 'Noisy Image']
# Multiply diffuse color by AO with the Mix node (CompositorNodeMixRGB was removed)
mix = nodes.new("ShaderNodeMix")
mix.data_type = "RGBA"
mix.blend_type = "MULTIPLY"
mix.inputs["Factor"].default_value = 0.5
links.new(rl.outputs["Diffuse Color"], mix.inputs["A"])
links.new(rl.outputs["Ambient Occlusion"], mix.inputs["B"])
# mix.outputs["Result"] carries the color result
Set data_type before linking: inputs["A"] resolves to the socket that is active for the current type.
# Alpha Over: always address the inputs by name, their order changed in 5.0
alpha_over = nodes.new("CompositorNodeAlphaOver")
alpha_over.inputs["Factor"].default_value = 1.0
# links.new(plate.outputs["Image"], alpha_over.inputs["Background"])
# links.new(rl.outputs["Image"], alpha_over.inputs["Foreground"])
# Keying node: green screen removal
keying = nodes.new("CompositorNodeKeying")
keying.inputs["Key Color"].default_value = (0.1, 0.8, 0.2, 1.0)
keying.inputs["Black Level"].default_value = 0.1
keying.inputs["White Level"].default_value = 0.9
# Color Spill: remove green fringing after keying
spill = nodes.new("CompositorNodeColorSpill")
spill.inputs["Spill Channel"].default_value = "G"
spill.inputs["Limit Method"].default_value = "Average"
file_out = nodes.new("CompositorNodeOutputFile")
file_out.directory = "/tmp/comp_output/"
file_out.file_name = "shot010_passes"
file_out.format.media_type = "MULTI_LAYER_IMAGE"
file_out.format.file_format = "OPEN_EXR_MULTILAYER"
file_out.file_output_items.new("RGBA", "Diffuse")
file_out.file_output_items.new("FLOAT", "AO")
links.new(rl.outputs["Diffuse Color"], file_out.inputs["Diffuse"])
links.new(rl.outputs["Ambient Occlusion"], file_out.inputs["AO"])
The frame number is appended only for animation renders (blender -b -a, -f 12, or render(animation=True)); put #### in file_name to force it.
| Blender 4.x | Blender 5.x |
|---|---|
scene.use_nodes = True; scene.node_tree | scene.compositing_node_group = bpy.data.node_groups.new(name, "CompositorNodeTree") |
CompositorNodeComposite | NodeGroupOutput plus a Color socket on tree.interface |
CompositorNodeMixRGB, Gamma, Math, Value, MapRange | ShaderNodeMix, ShaderNodeGamma, ShaderNodeMath, ShaderNodeValue, ShaderNodeMapRange |
glare.glare_type = 'BLOOM', blur.size_x, cb.lift, keying.clip_black | node.inputs[...] as shown above |
file_out.base_path, file_slots.new() | directory, file_name, file_output_items.new(type, name) |
Alpha Over inputs[1] / inputs[2] | inputs["Background"] / inputs["Foreground"] |
Pass outputs DiffCol, AO | Diffuse Color, Ambient Occlusion |
scene.use_nodes still exists in 5.x but always returns True, does nothing, and is scheduled for removal in 6.0. Blender 4.5 LTS already has the option inputs and keeps the old properties as deprecated, so it is the version to port on.
User request: "Add a cinematic look — warm highlights, cool shadows, bloom, and vignette"
import bpy
scene = bpy.context.scene
tree = bpy.data.node_groups.new("Cinematic Grade", "CompositorNodeTree")
scene.compositing_node_group = tree
nodes, links = tree.nodes, tree.links
rl = nodes.new("CompositorNodeRLayers")
# Color Balance
cb = nodes.new("CompositorNodeColorBalance")
cb.location = (250, 0)
cb.inputs["Type"].default_value = "Lift/Gamma/Gain"
cb.inputs["Color Lift"].default_value = (0.92, 0.93, 1.0, 1.0)
cb.inputs["Color Gain"].default_value = (1.15, 1.08, 0.95, 1.0)
links.new(rl.outputs["Image"], cb.inputs["Image"])
# Bloom
glare = nodes.new("CompositorNodeGlare")
glare.location = (500, 0)
glare.inputs["Type"].default_value = "Bloom"
glare.inputs["Threshold"].default_value = 0.7
glare.inputs["Size"].default_value = 0.7
links.new(cb.outputs["Image"], glare.inputs["Image"])
# Vignette (Ellipse Mask -> Blur -> Multiply)
ellipse = nodes.new("CompositorNodeEllipseMask")
ellipse.location = (300, -400)
ellipse.inputs["Size"].default_value = (0.85, 0.85)
blur_mask = nodes.new("CompositorNodeBlur")
blur_mask.location = (500, -400)
blur_mask.inputs["Size"].default_value = (200, 200)
links.new(ellipse.outputs["Mask"], blur_mask.inputs["Image"])
mix_vig = nodes.new("ShaderNodeMix")
mix_vig.location = (750, 0)
mix_vig.data_type = "RGBA"
mix_vig.blend_type = "MULTIPLY"
mix_vig.inputs["Factor"].default_value = 0.6
links.new(glare.outputs["Image"], mix_vig.inputs["A"])
links.new(blur_mask.outputs["Image"], mix_vig.inputs["B"])
out = nodes.new("NodeGroupOutput")
out.location = (1000, 0)
tree.interface.new_socket(name="Image", in_out="OUTPUT", socket_type="NodeSocketColor")
links.new(mix_vig.outputs["Result"], out.inputs["Image"])
scene.render.filepath = "/tmp/comp_output/graded_"
bpy.ops.render.render(write_still=True)
blender --background ~/projects/alley_shot/alley.blend --python grade.py
Result: the render log ends with Saved: '/tmp/comp_output/graded_.png'. The image has blue-tinted shadows, warm highlights, glow around pixels brighter than 0.7, and corners darkened by up to 60%.
User request: "Composite my 3D character over a photo background"
import bpy
scene = bpy.context.scene
scene.render.film_transparent = True # Use alpha instead of keying
tree = bpy.data.node_groups.new("Plate Comp", "CompositorNodeTree")
scene.compositing_node_group = tree
nodes, links = tree.nodes, tree.links
rl = nodes.new("CompositorNodeRLayers")
plate = nodes.new("CompositorNodeImage")
plate.location = (0, -400)
plate.image = bpy.data.images.load(bpy.path.abspath("//plates/rooftop_plate.jpg"))
alpha_over = nodes.new("CompositorNodeAlphaOver")
alpha_over.location = (400, -100)
links.new(plate.outputs["Image"], alpha_over.inputs["Background"])
links.new(rl.outputs["Image"], alpha_over.inputs["Foreground"])
out = nodes.new("NodeGroupOutput")
out.location = (650, -100)
tree.interface.new_socket(name="Image", in_out="OUTPUT", socket_type="NodeSocketColor")
links.new(alpha_over.outputs["Image"], out.inputs["Image"])
Result: the next render shows the character over rooftop_plate.jpg. The compositor works at the render resolution, so a plate with a different size is centered and cropped or padded; when the sizes differ, add a CompositorNodeScale node after the Image node with inputs["Type"].default_value = "Render Size" (its default, "Relative" at 1.0, changes nothing) and inputs["Frame Type"] set to "Crop" or "Fit" to keep the aspect ratio.
scene.compositing_node_group. scene.node_tree raises AttributeError in 5.x and scene.use_nodes = True no longer creates anything.scene.render.use_compositing = False switches compositing off.ellipse.width = 0.85 now sets the node's width in the editor, because width is a property of every node. Removed properties such as glare.glare_type raise AttributeError.TypeError: enum "BLOOM" not found in ('Bloom', ...) on a wrong value; the message lists the accepted labels.Lift, Keying Blur Size), inputs["Lift"] returns the first one. Use the identifier ("Color Lift", "Postprocess Blur Size"), which inputs[...] also accepts, and never numeric indices: socket order changes between releases.rl.outputs["Diffuse Color"] raises KeyError (the socket is disabled in 5.0, where name lookup skips it, and absent in 5.2).film_transparent = True with Alpha Over instead of Keying when you control the 3D scene.scene.render.compositor_device = 'CPU' explicitly. The default differs between releases, and the GPU compositor needs a working GPU context, which servers and containers often lack.node.location) are for visual layout only — they don't affect functionality.