Shader nodes, PBR materials, and procedural textures in Blender. Use for material creation, shader setups, PBR workflows, cross-engine compatibility (EEVEE_NEXT/Cycles), or when user mentions "material," "shader," "PBR," "texture," "node tree," or "Principled BSDF."
Installs into .claude/skills of the current project.
Are you the author of Blender Materials Shaders?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sideshowroberto-blender-materials-shaders)
---
name: blender-materials-shaders
description: Shader nodes, PBR materials, and procedural textures in Blender. Use for material creation, shader setups, PBR workflows, cross-engine compatibility (EEVEE_NEXT/Cycles), or when user mentions "material," "shader," "PBR," "texture," "node tree," or "Principled BSDF."
allowed-tools: Read,Write
---
# Blender Materials & Shaders Skill
**Version:** 2.1.0
**Last Updated:** 2026-09-10
**Dependencies:** Blender 5.1+
---
## Breaking Changes (4.5+ / 5.1+)
```python
import bpy
# Render engine (5.1+)
bpy.context.scene.render.engine = 'BLENDER_EEVEE_NEXT' # Not 'BLENDER_EEVEE'
# Principled BSDF input names changed in 4.5.0 - use names not indices
bsdf.inputs['Transmission Weight'].default_value = 1.0 # was 'Transmission'
bsdf.inputs['Subsurface Weight'].default_value = 0.2 # was 'Subsurface'
bsdf.inputs['Emission Color'].default_value = (1,1,1,1) # was 'Emission'
bsdf.inputs['Base Color'].default_value = (0.8, 0.2, 0.2, 1.0)
```
---
## Procedural texturing on parametric geometry (loop-gauntlet lessons, 2026-09-10)
- **Use Texture Coordinate > Object, not the default Generated
coordinates, for procedural textures on parametric architecture.** A
Noise texture on Generated coordinates stretched into streaks on
120-segment arc rings and long boxes; Object coordinates are metric and
isotropic because all objects share the world origin.
- **Brick texture reads the X/Y of its input vector - it is planar in that
plane, not aligned to the surface it is applied to.** On a wall lying in
the YZ plane a Brick texture produces blotches instead of coursing. Build
a Z-based course mask instead (SeparateXYZ > multiply > fract >
less-than > bump) - it works on every wall orientation and lines up
scene-wide.
- **A texture that shares a shader with an already-accepted plate must
share its histogram.** Before assigning a new image plate to a shader
slot that an accepted plate already uses, match the new plate's
luminance mean and standard deviation to the accepted one (a two-line
PIL script: read both images, scale/offset the new one to match mean and
std). Two extra relief plates with greyscale means of 126 and 172 against
an accepted plate's 181 rendered as dark grey stone instead of matching
stone, costing a full iteration before the mismatch was found.
- **A photo's surface grain becomes an unwanted bump map at close range.**
A photo-derived plate used as colour-plus-bump reads as rough bark
texture when the nearest matched camera is close (about 2 m); reserve
photo-derived plates for bands whose nearest camera is several metres
away, and use the smoothest (e.g. generated) plate on close-up bands.
---
## QUICK START
### Create PBR Material
```python
import bpy
mat = bpy.data.materials.new("PBR_Material")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs['Base Color'].default_value = (0.8, 0.2, 0.2, 1.0)
bsdf.inputs['Roughness'].default_value = 0.5
bsdf.inputs['Metallic'].default_value = 0.0
output = nodes.new('ShaderNodeOutputMaterial')
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
# Assign to active object
obj = bpy.context.active_object
if obj and obj.type == 'MESH':
obj.data.materials.append(mat)
print(f"Material '{mat.name}' assigned to '{obj.name}'")
```
---
## STANDARD WORKFLOWS
### Workflow 1: Procedural Texture (No Image Files)
**Use When:** Natural materials (stone, wood, rust) without image textures
```python
import bpy
mat = bpy.data.materials.new("Procedural_Stone")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
texcoord = nodes.new('ShaderNodeTexCoord')
noise = nodes.new('ShaderNodeTexNoise')
noise.inputs['Scale'].default_value = 5.0
noise.inputs['Detail'].default_value = 8.0
noise.inputs['Roughness'].default_value = 0.6
ramp = nodes.new('ShaderNodeValToRGB')
ramp.color_ramp.elements[0].color = (0.2, 0.2, 0.2, 1.0) # dark stone
ramp.color_ramp.elements[1].color = (0.7, 0.7, 0.7, 1.0) # light stone
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs['Roughness'].default_value = 0.85
output = nodes.new('ShaderNodeOutputMaterial')
links.new(texcoord.outputs['Object'], noise.inputs['Vector'])
links.new(noise.outputs['Fac'], ramp.inputs['Fac'])
links.new(ramp.outputs['Color'], bsdf.inputs['Base Color'])
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
print("Procedural stone material created")
```
---
### Workflow 2: Glass / Transparent Material
```python
import bpy
mat = bpy.data.materials.new("Glass")
mat.use_nodes = True
mat.blend_method = 'BLEND'
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
bsdf.inputs['Transmission Weight'].default_value = 1.0 # 4.5+ name
bsdf.inputs['IOR'].default_value = 1.45
bsdf.inputs['Roughness'].default_value = 0.0
output = nodes.new('ShaderNodeOutputMaterial')
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
# EEVEE_NEXT: enable ray tracing for refractions
bpy.context.scene.eevee.use_raytracing = True
print("Glass material created")
```
---
### Workflow 3: Material Layering with Mix Shader
**Use When:** Combining materials (metal + rust, clean + dirty, worn + fresh)
```python
import bpy
mat = bpy.data.materials.new("Layered_Metal")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
nodes.clear()
# Clean metal
bsdf_metal = nodes.new('ShaderNodeBsdfPrincipled')
bsdf_metal.inputs['Metallic'].default_value = 1.0
bsdf_metal.inputs['Roughness'].default_value = 0.2
bsdf_metal.inputs['Base Color'].default_value = (0.7, 0.7, 0.7, 1.0)
# Rust / weathering layer
bsdf_rust = nodes.new('ShaderNodeBsdfPrincipled')
bsdf_rust.inputs['Base Color'].default_value = (0.5, 0.2, 0.1, 1.0)
bsdf_rust.inputs['Roughness'].default_value = 0.9
# Noise-based mask
noise = nodes.new('ShaderNodeTexNoise')
noise.inputs['Scale'].default_value = 10.0
mix = nodes.new('ShaderNodeMixShader')
output = nodes.new('ShaderNodeOutputMaterial')
links.new(noise.outputs['Fac'], mix.inputs['Fac'])
links.new(bsdf_metal.outputs['BSDF'], mix.inputs[1])
links.new(bsdf_rust.outputs['BSDF'], mix.inputs[2])
links.new(mix.outputs['Shader'], output.inputs['Surface'])
print("Layered metal/rust material created")
```
---
### Workflow 4: Reusable Node Group
```python
import bpy
# Create a reusable weathering node group
group = bpy.data.node_groups.new("Weathering", 'ShaderNodeTree')
group_in = group.nodes.new('NodeGroupInput')
group_out = group.nodes.new('NodeGroupOutput')
# Interface (5.1+ uses interface API)
iface = group.interface
iface.new_socket("Scale", in_out='INPUT', socket_type='NodeSocketFloat')
iface.new_socket("Mask", in_out='OUTPUT', socket_type='NodeSocketColor')
noise = group.nodes.new('ShaderNodeTexNoise')
group.links.new(group_in.outputs['Scale'], noise.inputs['Scale'])
group.links.new(noise.outputs['Color'], group_out.inputs['Mask'])
# Use the group in a material
mat = bpy.data.materials.new("Weathered")
mat.use_nodes = True
nodes = mat.node_tree.nodes
node_group = nodes.new('ShaderNodeGroup')
node_group.node_tree = group
node_group.inputs['Scale'].default_value = 5.0
print(f"Node group '{group.name}' created and referenced")
```
---
## ADVANCED TECHNIQUES
### Cross-Engine Optimization
```python
import bpy
engine = bpy.context.scene.render.engine
if engine == 'BLENDER_EEVEE_NEXT':
# EEVEE: performance-conscious, keep it simple
mat.refraction_depth = 0.1
# Real-time: avoid expensive SSS and volumetrics unless needed
else: # CYCLES
# Full ray tracing - enable SSS, caustics, etc.
bsdf.inputs['Subsurface Weight'].default_value = 0.1
bsdf.inputs['Subsurface Radius'].default_value = (0.1, 0.05, 0.03)
```
---
## TROUBLESHOOTING
### Material Not Appearing in Viewport
```python
import bpy
# Set viewport to Material Preview or Rendered shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL' # NOT 'MATERIAL_PREVIEW' (4.5+)
```
### Shader Socket AttributeError
```python
# 'NodeSocketShader' has no default_value - it only accepts links
# [FAIL] bsdf.outputs['BSDF'].default_value = ...
# [OK] Just connect it:
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
```
### Material Renders Black
```python
import bpy
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# Verify output node exists
output = next((n for n in nodes if n.type == 'OUTPUT_MATERIAL'), None)
if not output:
output = nodes.new('ShaderNodeOutputMaterial')
# Ensure BSDF is connected to surface
if not output.inputs['Surface'].is_linked:
bsdf = next((n for n in nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf:
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
print("Fixed: reconnected BSDF to output")
```
---
## VALIDATION CHECKLIST
- [ ] Engine set (`BLENDER_EEVEE_NEXT` or `CYCLES`)
- [ ] BSDF inputs use 4.5+ naming convention
- [ ] Material Output node connected
- [ ] Viewport shading = `MATERIAL` (not `MATERIAL_PREVIEW`)
- [ ] Test render validates appearance
---
## VERSION HISTORY
**v2.1.0** (2026-09-10) - Loop-gauntlet lessons: procedural textures need
Object coordinates (not Generated) on parametric geometry, Brick texture is
planar-XY (use a Z-based course mask for walls), and image plates sharing a
shader with an accepted plate must be histogram-matched, with photo-derived
plates reserved for bands far from the nearest camera.
**v2.0.0** (2026-06-10) - MCP migration
- Removed HTTP Bridge requirements and curl steps
- Removed `import requests` / `requests.post()` wrappers
- Added `import bpy` to all code blocks
- Updated target: Blender 5.1+
- Added node group workflow with 5.1+ interface API
**v1.0.0** (2025-10-24) - Initial release
---
**Status:** Production Ready
**Maintainer:** VFX Pipeline Team
**Dependencies:** Blender 5.1+, Python 3.11+