Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shaders and Compositing

Geometry Script can also build shader and compositor node trees. Each tree type has its own module, because each accepts a different set of nodes:

ModuleDecoratorsBuilds
geometry_script@tree (or @geometry)Geometry Nodes groups
geometry_script.shader@shader, @material, @worldShader node groups, materials and worlds
geometry_script.compositor@compositorCompositor node groups

Each module has every node its tree type accepts as a typed function, plus the same types (Color, Vector, Shader, …) and Socket arithmetic as the main module. Some names exist in several modules with different nodes behind them. For example, normal() is the Normal input in Geometry Nodes, but the Normal vector node in shaders. Import each module where it’s used. If a node is used in the wrong kind of tree, the error says which module it belongs to.

from geometry_script.shader import *   # A shader-only script
from geometry_script import *          # Geometry nodes
from geometry_script import shader as sh

sh.principled_bsdf(...)                # Shader nodes, behind a namespace

Materials

@material builds a material from a function with no parameters. Return the surface shader:

from geometry_script.shader import *

@material("Gold")
def gold():
    return principled_bsdf(
        base_color=(1.0, 0.77, 0.34, 1.0),
        metallic=1.0,
        roughness=0.2,
    ).bsdf

To use the other inputs of the Material Output, return a dictionary:

@material("Rough Stone")
def rough_stone():
    height = noise_texture(scale=8.0).factor
    return {
        "surface": principled_bsdf(base_color=(0.4, 0.4, 0.38, 1.0)).bsdf,
        "displacement": displacement(height=height, scale=0.05).displacement,
    }

The material is rebuilt in place each time the script runs, so objects that use it keep it. Calling the decorated function returns the material, so a geometry tree can assign it:

@tree("Golden Cube")
def golden_cube():
    return set_material(geometry=cube().mesh, material=gold()).geometry

Shader Node Groups

@shader builds a reusable shader node group. It works like @tree: parameters become group inputs, return values become group outputs, and calling it while building a material, world or another shader group adds a group node.

@shader("Stripes")
def stripes(color_a: Color, color_b: Color, scale: float = 6.0):
    wave = wave_texture(scale=scale)
    return mix(data_type="RGBA", factor=wave.factor, a=color_a, b=color_b).result

@material("Striped")
def striped():
    color = stripes((0.9, 0.3, 0.1, 1.0), (0.1, 0.1, 0.1, 1.0), scale=10.0)
    return principled_bsdf(base_color=color).bsdf

Materials and worlds have no inputs of their own, so put anything you’d like to adjust in Blender’s UI into a @shader group.

@repeat_zone and @closure_zone work in shader trees too.

Worlds

@world builds a world the same way. Pass scene=True to make it the active scene’s world:

@world("Studio", scene=True)
def studio():
    return background(color=(0.05, 0.06, 0.08, 1.0), strength=1.0).background

Compositing

@compositor builds a compositor node group from geometry_script.compositor. Pass scene=True to use it as the active scene’s compositor. Its first output is the final image:

from geometry_script.compositor import *

@compositor("Soft Glow", scene=True)
def soft_glow(strength: float = 0.3):
    image = render_layers().image
    glow = blur(image=image, size=(20, 20)).image
    return mix(data_type="RGBA", blend_type="SCREEN", factor=strength, a=image, b=glow).result

Without scene=True, the group can be used as a node in other compositor groups, just like a @tree inside another @tree.