---
name: 3d-material
description: End-to-end PBR material authoring in Blender. Principled BSDF setup with physically valid parameters, texture map import with correct color spaces, normal-map chain, baking (high-to-low, Selected-to-Active, cage), node-based layering with Mix Shader chains, procedural masks (edge wear via Pointiness, snow via Normal Z, Fresnel rims), and ORM packing for game export. Use when the user mentions PBR, Principled BSDF, shader, material, texture map, albedo, base color, roughness, metallic, normal map, AO, color space (sRGB vs Non-Color), baking, Selected to Active, cage object, Mix Shader, Add Shader, edge wear, dirt, weathering, ORM packing, material export for Unity / Unreal / glTF, washed-out colors, metallic looking wrong on wood/plastic/stone, normal map appears yellow-green or inverted, dark crevices that ignore lighting, or any material that "looks off" under any lighting.
---

# 3D Material — Principled BSDF → Textures → Layering

End-to-end PBR material pipeline. Three phases, three quality gates. The most parameter-sensitive skill in the 3D suite: a single wrong color space corrupts every downstream calculation, and a single non-zero Metallic on a dielectric breaks the physical basis of shading more completely than most topology errors.

This skill assumes `foundations.md` (color space table, PBR parameter ranges, four universal rules, seven-step pre-action checklist) is already in working memory. Do not duplicate those tables here — reference them.

## When to use

- Building or fixing any PBR material in Blender (Principled BSDF, shader graph).
- Importing texture sets (albedo / roughness / metallic / normal / AO / displacement / emission / opacity).
- Baking high-to-low normal maps (Selected-to-Active or cage workflow).
- Debugging color-space symptoms: washed-out albedo, yellow-green normal maps, roughness that "doesn't respond," dark crevices in any lighting.
- Layering materials — chained Mix Shaders, mask-driven blends, edge wear, dirt accumulation, snow on tops, Fresnel rims.
- Preparing materials for game export — ORM packing, PNG data maps, OpenGL vs DirectX normal handedness.

## When NOT to use

- Pure geometry, topology, retopology, or edge flow work — route to `3d-character` or `3d-hard-surface`.
- UV unwrapping in isolation (no material context) — handled inside the modeling skill that owns the mesh, with material-relevant rules covered here.
- Lighting setup, HDRI, three-point, render settings, denoising — route to `3d-lighting-render`.
- Generating procedural geometry (Geometry Nodes scatter, not procedural shading) — route to `3d-environment`.
- Writing batch material scripts across many .blend files — route to `3d-automation`.

## Prerequisites

- `foundations.md` color space table (§4) and PBR parameter ranges (§5).
- UV-prepped mesh: non-overlapping islands, every hard edge marked as a UV seam (non-negotiable for baking — without this, every bake shows bright seams along the hard edge).
- All transforms applied on every mesh involved in baking (`Ctrl+A > All Transforms`). Unapplied scale produces unpredictable ray casts.
- Cycles render engine for baking. Eevee cannot bake.
- For game export: target engine's normal convention known (OpenGL = standard, DirectX = Invert G).

## Pipeline overview

```
Phase 1: Principled BSDF        → Quality Gate 1 (parameter sanity)
Phase 2: Texture import + bake  → Quality Gate 2 (color space + chain audit)
Phase 3: Layering + export      → Quality Gate 3 (energy conservation + packing)
```

Working in reverse forces full revalidation. A texture color-space fix late in Phase 3 invalidates every downstream layering decision built on its (wrong) values.

---

## Phase 1: Principled BSDF with Physically Valid Parameters

Create the material, enable Use Nodes, confirm Principled BSDF is the default and only the default — no Diffuse BSDF, no Specular BSDF, no legacy nodes unless there is a specific reason.

### Parameter targets

Reference `foundations.md` §5 for the canonical table. Operational summary:

| Surface class | Base Color (sRGB) | Metallic | Roughness | Special |
|---|---|---|---|---|
| Non-metal (wood, plastic, stone, paint, fabric) | 60–240 | **0.0** | 0.3–0.9 | Specular = 0.5 |
| Metal (iron, copper, gold, aluminum) | 180–250 | **1.0** | 0.1–0.4 | Color = specular tint |
| Glass / transparent | n/a | 0.0 | 0.0–0.1 | Transmission Weight = 1.0, IOR = 1.45 |
| Skin (SSS) | flesh tones in non-metal range | 0.0 | 0.4–0.6 | Subsurface Weight 0.1–0.5, Radius (1.0, 0.4, 0.1) |
| Clearcoat / car paint | base in non-metal range | 0 or 1 | base layer | Coat = 1.0, Coat Roughness independent |

### Distribution

Set Distribution to **Multiscatter GGX**, not standard GGX. Standard GGX leaks energy at high roughness; Multiscatter GGX preserves energy conservation across the full roughness range.

### Transitional surfaces

Intermediate Metallic values (0.1–0.9) are reserved for transitional states ONLY: dust on metal, rust on steel, oxidation along an edge. Treat them as a code smell — if you find yourself sliding Metallic to 0.5 to "balance" a wood material, you have made a categorical error.

### Quality Gate 1

- Metallic is exactly 0.0 or exactly 1.0 (unless explicitly transitional, with a documented reason).
- Base Color sampled in the Image Editor falls inside the valid sRGB ranges (60–240 non-metal, 180–250 metal).
- Distribution = Multiscatter GGX.
- Specular = 0.5 for dielectrics; not touched for metals (it has no effect when Metallic = 1.0).

If any of these fail, fix before moving on. Texture maps applied over a broken BSDF only obscure the underlying error.

---

## Phase 2: Texture Map Import, Normal-Map Chain, Baking

The single most violated rule in PBR. Color space configuration determines whether the shader receives correct physical values or gamma-distorted garbage.

### Color space rules (see foundations.md §4)

- **sRGB:** Albedo / Base Color, Emission.
- **Non-Color:** Roughness, Metallic, Normal, Ambient Occlusion, Displacement, Opacity / Alpha mask, any grayscale mask used as a Mix Shader factor.

Mis-assignment symptoms:
- Albedo on Non-Color → washed-out, desaturated colors.
- Roughness on sRGB → roughness "doesn't respond" or material reads too smooth/too rough at midtones (gamma distortion of value range).
- Normal map on sRGB → washed-out yellow-green appearance instead of saturated purple-blue, broken shading directions, lighting that looks wrong at edges.

### Normal-map chain (non-negotiable)

```
Image Texture (Non-Color)
        │
        ▼
   Normal Map (Tangent space)
        │
        ▼
   Principled BSDF — Normal socket
```

Three nodes, in this exact order, every time. Skipping the Normal Map node and wiring Image Texture directly to the BSDF Normal socket produces incorrect tangent-space decoding.

**DirectX-format normal maps** (common from Substance Painter with the wrong export preset, from older asset stores, from some Unreal pipelines): enable **Invert G** on the Normal Map node. Symptom of forgetting this: lighting inverted along the green channel — bumps that should pop forward appear pushed inward.

### Baking (high-to-low)

Pre-flight (apply everything BEFORE bake):
1. All transforms applied on both meshes (`Ctrl+A > All Transforms`).
2. Low-poly has clean, non-overlapping UVs.
3. **Every hard edge has a corresponding UV seam.** Without this, the bake shows bright seams along every hard edge. The rule is absolute, not a guideline.
4. Low-poly material has an Image Texture node pointing at the target bake image, and that node is **selected** in the shader editor.
5. Render engine = Cycles.

Selection and bake:
1. Select **high-poly first**.
2. Shift-select **low-poly last** (becomes the active object).
3. Bake panel: enable **Selected to Active**.
4. Bake Type = **Normal**.
5. Set **Extrusion** or **Ray Distance**: start at 0, increase incrementally until the high-poly is fully enclosed within the bake envelope.
6. Click Bake.

**Cage workflow (use for complex geometry, overlaps, tight gaps):**
1. Duplicate the low-poly.
2. Expand it outward with `Alt+S` (shrink/fatten along normals) or a Shrinkwrap modifier in reverse, until it fully encloses the high-poly with margin.
3. In the Bake panel, enable Cage and reference the cage object by name.
4. Rays now cast from the cage surface rather than an extruded shell — eliminates ray misses in concavities and erroneous intersections.

### File formats

- **Data maps** (Roughness, Metallic, Normal, AO, Displacement, Opacity): **PNG (lossless)**. 16-bit PNG for Normal and Displacement where precision matters. Never JPEG — lossy compression corrupts encoded physical data and produces visible artifacts in 3D even if the texture looks fine in 2D preview.
- **Albedo / Emission:** PNG preferred; JPEG acceptable when file size is a hard constraint.

### Never bake AO into albedo

AO must remain a separate map and be multiplied with the lighting result in the shader graph (typically AO → Multiply node → Base Color input, or AO routed through the dedicated AO socket on Principled BSDF if available). When AO is baked into base color, crevices stay dark under direct illumination because the "shadow" is part of the surface color rather than a lighting effect — they look dead under any lighting.

### Quality Gate 2

- Every Image Texture node in every material has its color space set correctly per the table above. **Run the color-space audit script** (see `color-space-audit-script.md`) before declaring this gate passed.
- Normal map chain is complete: Image Texture (Non-Color) → Normal Map (Tangent) → BSDF Normal socket. Invert G enabled when source is DirectX format.
- No AO baked into any albedo texture. AO is a separate map.
- All data maps are PNG. No JPEG on Roughness / Metallic / Normal / AO / Displacement / Opacity.
- Baked normal maps show no bright seams along hard edges (hard edges = UV seams was honored).
- Baked normal maps show no pure-black regions (extrusion or cage was sufficient).

---

## Phase 3: Node Layering, Procedural Detail, Export

Real-world objects are rarely a single homogeneous material. Layering blends multiple shader networks based on spatial masks.

### Mix Shader, not Add Shader

**Mix Shader** interpolates between two shaders by a Factor 0–1, preserving energy conservation. **Add Shader** adds light contributions — useful only for layering emission on top of a base material. Using Add Shader for material blending makes materials look too bright / glowing because total reflectance can exceed incident light.

To layer three or more materials: cascade Mix Shaders. The output of one Mix Shader feeds the first input of the next.

```
Base ──┐
       ├─ Mix Shader (Factor = mask_A) ──┐
Wear ──┘                                  ├─ Mix Shader (Factor = mask_B) ── Output
                                Dirt ────┘
```

### Mix Shader vs Mix Color

- **Mix Shader** blends complete shader outputs (BSDF closures). Use this for blending entire materials with different Metallic / Roughness / Normal properties.
- **Mix Color** (MixRGB) blends color values BEFORE they reach a shader. Use this for blending texture inputs (e.g. two albedo maps) inside a single shader.

Confusing the two produces wrong results. Different Metallic values across a layer transition require Mix Shader, not Mix Color.

### Mask sources (ordered by typical use)

1. **Image textures** — grayscale masks, Non-Color color space. Most common, most controllable.
2. **Vertex colors** — Attribute node retrieves vertex color data. Works identically in Eevee and Cycles. Useful for game assets where texture memory is constrained or the mask must deform with the mesh.
3. **Procedural textures** — Noise, Voronoi, Musgrave. Infinite resolution, seamless 3D tiling, no UV dependency. Best for organic weathering, dirt accumulation, edge wear that should not repeat.
4. **Geometry signals:**
   - **Geometry > Pointiness** — convex edges only. The canonical edge-wear mask.
   - **Normal Z** (or `Geometry > Normal` separated into Z) — upward-facing surfaces only. Snow on tops, dust accumulation.
   - **Layer Weight (Fresnel / Facing)** — Fresnel-driven rim effects, grazing-angle dust, velvet sheen masks.

Procedural masks are almost always combined with **ColorRamp** (to constrict the value range to a narrow band) and **Math** nodes (Multiply for global wear amount, Add for bias, Power for contrast curves).

### Edge-wear recipe (canonical)

```
Geometry > Pointiness
        │
        ▼
   ColorRamp (squeeze stops to a narrow band near the convex end)
        │
        ▼
   Math (Multiply) × global_wear_factor
        │
        ▼
   Mix Shader factor: base material → worn material
```

Optionally multiply by an inverted vertex paint mask to keep wear off undersides or hidden faces. Optionally multiply by a procedural Noise to break up uniformity.

See `edge-wear-recipes.md` for variants (paint chips, rust crawl, polish on high points).

### Export packing — ORM

For game engines (Unity, Unreal, glTF), pack three grayscale data channels into a single RGB texture:

- **R** = Ambient Occlusion
- **G** = Roughness
- **B** = Metallic

This is the industry-standard ORM convention. It reduces texture sampler count, halves bandwidth versus three separate grayscale maps, and is the expected import format for glTF / GLB and most modern PBR pipelines.

Pack in Blender via the Compositor or an external tool (Substance Painter, Photoshop, Krita) by routing each grayscale source into a single RGB image's red, green, and blue channel respectively.

### Quality Gate 3

- Material blending uses **Mix Shader**. **Add Shader** appears only where energy addition is physically explicit — typically emission added on top of a base material.
- Every Mix Shader chain has a clearly identifiable mask source (image / vertex color / procedural / geometry signal). No "magic" factors.
- Procedural textures pass through ColorRamp and/or Math nodes — raw procedural output is rarely the right factor without contrast / bias tuning.
- For game export targets, ORM packing is applied. Three separate grayscale maps shipped to Unity / Unreal / glTF is a memory and sampler waste.
- Material naming traces to parent mesh: `m_prop_barrel_main`, not `red_shiny_metal`. Descriptive naming breaks when art direction changes; functional naming survives.

---

## Color Space Audit (programmatic check)

A short scripted audit catches the most expensive failure mode in this skill: a single wrong color space silently corrupting every downstream calculation. Pseudocode:

```
for image in bpy.data.images:
    for material in bpy.data.materials that reference image:
        node = the Image Texture node holding this image
        expected = lookup(map_type_of(node))  # by socket connection or filename heuristic
        actual   = image.colorspace_settings.name
        assert actual == expected, f"{image.name}: expected {expected}, got {actual}"
```

Heuristic for `map_type_of(node)`:
- Connected to Base Color socket → expect sRGB.
- Connected to Emission socket → expect sRGB.
- Connected to a Normal Map node → expect Non-Color.
- Connected to Roughness / Metallic / Alpha / Displacement / Mix Shader Fac → expect Non-Color.
- Filename contains `albedo`, `basecolor`, `diffuse`, `emit` → expect sRGB.
- Filename contains `rough`, `metal`, `normal`, `nrm`, `ao`, `occ`, `disp`, `height`, `mask`, `alpha`, `opacity`, `orm` → expect Non-Color.

Full annotated script and one-liner runner in `color-space-audit-script.md`. Run before declaring Quality Gate 2.

---

## Common failures (symptom → cause → fix)

| Symptom | Cause | Fix |
|---|---|---|
| Wood / plastic / stone reads as dark tarnished metal | Metallic > 0 on dielectric | Metallic = 0.0 |
| Normal map looks yellow-green / washed instead of saturated purple-blue | Image Texture color space = sRGB | Set to Non-Color |
| Roughness "doesn't respond" or midtones look wrong | Roughness map color space = sRGB | Set to Non-Color |
| Albedo looks washed-out / desaturated | Albedo color space = Non-Color | Set to sRGB |
| Material reads too bright / glowing under any light | Add Shader used for material blending, or albedo above 240 sRGB | Switch to Mix Shader; clamp albedo |
| Baked normal map has bright seams along hard edges | Hard edges not marked as UV seams | Add UV seam at every hard edge; re-bake |
| Baked normal map has pure-black patches | Extrusion / cage too small; ray misses | Increase Extrusion or use cage object enclosing high-poly |
| Crevices stay dark under direct light | AO baked into albedo | Bake AO separately; multiply in shader (or pack as ORM red channel) |
| DirectX normal map shows inverted green-channel bumps | Handedness mismatch | Enable Invert G on the Normal Map node |
| Bake produces feedback-loop noise | Normal Map node connected to material output during bake | Disconnect Normal Map before bake; reconnect after |
| Displacement shows visible banding | 8-bit displacement map | Use 16-bit or 32-bit PNG / EXR |
| Material works in Cycles, looks wrong in Eevee | Cycles-only features used (true displacement, Random Walk SSS, anisotropic SSS) | Bake displacement to normal map; use Christensen-Burley SSS; verify in target engine |

Full failure library: theory §13.4. PBR rules: theory §12.3.

---

## Execution modes

### Playbook mode (default)

Produce step-by-step instructions a human follows in Blender's UI. Describe Shader Editor node connections in text:
- "Add an Image Texture node (`Shift+A > Texture > Image Texture`). Load `wood_albedo.png`. Set Color Space to sRGB. Connect Color output to the Base Color input of Principled BSDF."
- "Add a Normal Map node (`Shift+A > Vector > Normal Map`). Insert it between the Image Texture (loaded with `wood_normal.png`, Color Space Non-Color) and the Principled BSDF Normal socket."

Default mode when no live Blender instance is detected.

### MCP mode (when Blender is live)

Detection: call `mcp__blender__get_scene_info`. If it returns a scene, MCP mode is available. Load tool schemas first via `ToolSearch select:mcp__blender__execute_blender_code,mcp__blender__get_scene_info,mcp__blender__get_object_info,mcp__blender__get_viewport_screenshot,mcp__blender__download_polyhaven_asset,mcp__blender__set_texture,mcp__blender__search_polyhaven_assets`.

Key patterns:

- **Build node trees programmatically:**
  ```python
  mat = bpy.data.materials.new("m_target")
  mat.use_nodes = True
  nodes = mat.node_tree.nodes
  links = mat.node_tree.links
  bsdf = nodes.get("Principled BSDF")
  tex = nodes.new("ShaderNodeTexImage")
  tex.image = bpy.data.images.load("/abs/path/wood_albedo.png")
  tex.image.colorspace_settings.name = "sRGB"
  links.new(tex.outputs["Color"], bsdf.inputs["Base Color"])
  ```
- **Audit color spaces:** iterate `bpy.data.images` and check `image.colorspace_settings.name`. Run the audit script (see `color-space-audit-script.md`) before any bake-or-export operation.
- **Bake:** call `bpy.ops.object.bake(type='NORMAL')`. Must run with the high-poly selected and the low-poly active. Validate selection state before calling.
- **Polyhaven shortcut:** `mcp__blender__download_polyhaven_asset(asset_type='texture', ...)` pulls a ready CC0 PBR set (albedo + roughness + normal + displacement + AO, correctly named). Then `mcp__blender__set_texture` assigns it to a target object. Search assets first with `mcp__blender__search_polyhaven_assets`.
- **Verify visually:** `mcp__blender__get_viewport_screenshot` with the viewport in Material Preview (HDRI) — fastest way to confirm albedo / roughness / normal are reading correctly before committing to a full render.
- **Inspect existing material state:** `mcp__blender__get_object_info` returns the object's material slot data, useful before patching shaders.

**MCP limitations to watch for:**
- `bpy.ops.object.bake` is blocking and can take minutes. Confirm scope with the user before kicking off a high-resolution bake.
- Some procedural / geometry-input nodes (Pointiness via `Geometry`) require Cycles to evaluate correctly even though they exist in Eevee — confirm the active render engine via `bpy.context.scene.render.engine` before relying on Pointiness output.
- Image color space changes via the Python API do not visually refresh active node previews until the shader is re-evaluated; verify via a viewport screenshot rather than trusting node thumbnails.
- The MCP server cannot upload arbitrary files in a single call — assume textures must exist on the host filesystem at known absolute paths.

Specific MCP tools used by this skill:
`mcp__blender__execute_blender_code`, `mcp__blender__get_scene_info`, `mcp__blender__get_object_info`, `mcp__blender__get_viewport_screenshot`, `mcp__blender__download_polyhaven_asset`, `mcp__blender__set_texture`, `mcp__blender__search_polyhaven_assets`.

---

## Source pointers

- Module spine: theory §15.4 (Material Authoring Module — three phases, three quality gates).
- PBR theory in depth: theory §7 (microfacet model, energy conservation, Disney BRDF, Fresnel, metallic vs specular workflow, map types, Blender material system, Mix Shader chains, Eevee vs Cycles, authoring rules).
- UV / normal-map chain / baking: theory §4 (especially §4.2.3 hard-edge–seam correspondence, §4.4.2 baking workflow with cage setup).
- Material and rendering rules: theory §12.3.
- Material and rendering failures: theory §13.4 (Metallic on non-metals, AO in albedo, Add Shader misuse, DirectX inversion, JPEG on data maps).
- Cross-cutting rules (universal): `foundations.md` (color space table §4, PBR parameter ranges §5, top failure modes §8, naming conventions §7).
- Supporting recipes in this skill directory: `color-space-audit-script.md`, `edge-wear-recipes.md`, `bake-cage-checklist.md`.
