---
name: "blender_material_shaders"
description: "Builds PBR materials with Blender's shader nodes; triggers when crafting Principled BSDF materials, procedural Noise/Voronoi textures, UV work for texturing, or choosing between normal maps and bump."
metadata: { "includeInPrompt": true }
---

# Blender Material Shaders

## Purpose
Craft production-ready PBR materials inside Blender using the Principled BSDF and procedural texture nodes. This skill covers material and shader work only.

Not this skill: headless-blender owns headless render ops ("Render with Blender on a headless VM: background mode, Cycles CPU, robust frame output, and common gotchas."); 3d-printing-basics owns FDM print prep ("Beginner FDM 3D printing workflow: slicer settings that matter, filament choice (PLA/PETG/ABS), first-layer and bed-adhesion troubleshooting, and common print-failure diagnosis."). This skill owns material and shader craft; nothing about printers or render farms.

## Workflow
1. Start from the Principled BSDF and set the core dials first: Base Color, Metallic, Roughness, Specular IOR Level. Add lobes deliberately afterward: Subsurface Weight for wax or skin, Transmission Weight for glass, Coat Weight for clearcoat. For pure glass, set the diffuse Weight to 0, or you get a milky veil over the transmission.
2. Drive the shader procedurally. Wire Texture Coordinate to Mapping to Noise, then feed Noise Fac into Roughness. Wire Texture Coordinate to Mapping to Voronoi, then Voronoi Distance through a ColorRamp into Base Color. Small Scale changes on the texture nodes reshape the whole look.
3. Get UVs right before texturing. Unwrap with seams first, then texture. For simple tiling adjustments, scale in the Mapping node instead of re-unwrapping. Use Generated or Object coordinates for surfaces that should not depend on a UV unwrap.
4. Pick normal map or bump by need. Use a Normal Map node with a tangent-space image texture for baked detail like sculpted creases. Use the Bump node fed by a procedural height value (Noise Fac, Voronoi Distance) for subtle surface variation with no UVs and no bake. Bump is cheap but fakes the surface, so it fails at silhouettes and close-ups.
5. Prove it with a render. Build the material in a script, render a small thumbnail, and look at it before calling it done.

## Output Contract
The deliverable is a short Blender Python script that builds the material from scratch when run headless (`blender -b -P build_material.py`), plus a rendered preview PNG of the material on a sphere. The script must run clean on Blender 5.1.1 (verified 2026-10-07: procedural Principled BSDF with Noise-driven roughness and Voronoi-driven color rendered a 256px thumbnail without errors).

Short reference script. Save as `build_material.py`, then run `blender -b -P build_material.py`:

```python
import bpy, math
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.device = 'CPU'
scene.cycles.samples = 32
scene.render.resolution_x = 256
scene.render.resolution_y = 256

bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0)
obj = bpy.context.active_object

mat = bpy.data.materials.new("ProceduralMat")
mat.use_nodes = True
nodes, links = mat.node_tree.nodes, mat.node_tree.links
nodes.clear()
out = nodes.new('ShaderNodeOutputMaterial'); out.location = (400, 0)
bsdf = nodes.new('ShaderNodeBsdfPrincipled'); bsdf.location = (100, 0)
links.new(bsdf.outputs['BSDF'], out.inputs['Surface'])
texc = nodes.new('ShaderNodeTexCoord'); texc.location = (-700, 0)
mapping = nodes.new('ShaderNodeMapping'); mapping.location = (-500, 0)
links.new(texc.outputs['UV'], mapping.inputs['Vector'])
vor = nodes.new('ShaderNodeTexVoronoi'); vor.location = (-300, 200)
vor.inputs['Scale'].default_value = 8.0
links.new(mapping.outputs['Vector'], vor.inputs['Vector'])
ramp = nodes.new('ShaderNodeValToRGB'); ramp.location = (-100, 200)
links.new(vor.outputs['Distance'], ramp.inputs['Fac'])
links.new(ramp.outputs['Color'], bsdf.inputs['Base Color'])
noise = nodes.new('ShaderNodeTexNoise'); noise.location = (-300, -200)
noise.inputs['Scale'].default_value = 5.0
links.new(mapping.outputs['Vector'], noise.inputs['Vector'])
links.new(noise.outputs['Fac'], bsdf.inputs['Roughness'])
obj.data.materials.append(mat)

bpy.ops.object.light_add(type='AREA', location=(2.5, -2.5, 3.0))
bpy.ops.object.camera_add(location=(0, -3.5, 1.2), rotation=(math.radians(78), 0, 0))
scene.camera = bpy.context.active_object
scene.render.filepath = "/tmp/material_preview.png"
bpy.ops.render.render(write_still=True)
```

## Operating Rules
1. Never invent node types, socket names, or menu paths. When unsure, print the real socket list: `[i.name for i in bsdf.inputs]` (on 5.1.1 this returns Base Color, Metallic, Roughness, IOR, Alpha, Normal, Weight, ..., Transmission Weight, Coat Weight, and more).
2. Colors set through `default_value` on shader sockets are interpreted as linear, not sRGB. Convert an intended sRGB pick to linear before assigning, or the material renders washed out.
3. The Principled BSDF lobes add up. Transmission Weight does not replace diffuse, so pure glass needs the diffuse Weight at 0. The same applies when layering coat or subsurface: every active lobe contributes.
4. On a Mix Shader node, Fac 0 selects the first shader input and Fac 1 selects the second. Verified by render 2026-10-07: Fac 0.0 rendered the first emission shader (red), Fac 1.0 rendered the second (green).
5. Confirm the local Blender version fresh with `blender --version` before quoting behavior, since node layout shifts between releases. The script above was verified on 5.1.1.
6. Label anything unverified as "verify on your machine" instead of asserting it.
