UV unwrapping and baking
SkillDev toolsUse when UV unwrapping, placing seams, straightening or packing UVs, setting texel density, laying out UDIMs or mirrored UVs, or baking normal, AO, curvature, ID or displacement maps from a sculpt or high poly onto a low poly or retopo in Blender, including Multires bakes. Also when a bake has black or inverted patches, flat areas, cracks, seams, wavy lines or AO ghosts, bpy.ops.object.bake returns CANCELED, or uv.unwrap silently fails.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the UV unwrapping and baking skill
What this skill tells your AI
The instructions your AI receives, as published by scenario-labs/skills in skills/dcc/blender/scenario-blender-uv-baking/SKILL.md and read by ahel’s review.
Expert-level UVs put seams where the texturing method can afford them, set texel density before texture size, and pack with pixel-true padding. Expert bakes measure their projection distances instead of guessing, and are judged in pixels and under a raking light, never eyeballed once. Every stage below has a measurable gate that scripts/bx_uvbake.py computes. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-blender-expert (execution channel, review loop, 5.2 API changes).
Stance (the expert delta)
- Seams follow the texturing method. Kaspar (2021, 2D painting): as few as possible, hidden under hair, clothes and inner limbs. Blender Studio guide (2024, same author): more seams rather than fewer when you paint in 3D or bake, because stretch costs more. Game bakes add a hard rule: every hard edge is a seam, or the normal map cracks (Lampel, Gambrell, On Mars 3D, Polycount).
- Only closed shells and long tubes need extra cuts. Morren: anything closed on itself needs one cut. Gambrell: cut every taped-paper ring once, hidden. But an island with holes (a face with eye holes, a washer) flattens fine.
cut_to_diskstest-unwraps it and cuts only if it stretches [added; cutting everything folded the sheep's big islands]. - Unwrap method is a measurement, and 5.2 defaults to CONFORMAL. Always pass
method=. On Suzanne, Minimum Stretch gives the most even density (spread 1.38), Angle Based the best angles, and Conformal fails by area (5.89). On flat hard-surface islands all three converge.unwrap(method="BEST")picks per island and rejects islands that fold over themselves. - Density first, texture size second (Huge Menace). Targets: FPS 10.24 px/cm, weapon 20.48, third person 5.12, top-down 2.56. Priority follows the camera: face, hands and front up; soles, inner mouth and back down (SpeedChar, Kaspar). Match texture sets by scaling the denser set down, never the other up (Gambrell).
- Straighten, then equalize, then pack without tilting (Lampel): Follow Active Quads from a truly rectangular quad, pin, re-unwrap the rest, pack with CARDINAL. Padding is a pixel number, res/128 (16 px at 2K), passed as FRACTION margin = gap/(2*res).
- Measure the projection, never guess extrusion, and do not trust one global value. Ryan King's 0.01 then 0.02 was scene-specific. SpeedChar swept six values by hand, and the value that fixed the face broke the armpit ("when we fix something somewhere, some other problems occur", E6QMhGuhyoc [00:26:23]). Rays start on the low, and
max_ray_distancecounts from the extruded start. The adaptive bake keeps, per texel, the smallest extrusion that hits a front face. On the sheep it cut inverted texels from 7.5% to 1.2% [verified]. - Unique UVs wherever baked lighting differs (SpeedChar: an AO ghost means shared texels). Stacked or mirrored copies move exactly one tile out of 0-1 before the bake (Polycount), because inside 0-1 the last island baked wins.
- A bake is judged in pixels and under light. Blue < 0.5 means rays hit the far side. Exactly flat means rays missed. All-white AO means the distance is too small. Inverted pixels that do not move when extrusion changes point to a geometry problem, not a setting [verified on the sheep]. Multires bakes cast no rays: on the same surface they gave 0.09% inverted texels.
Establish first
| Input | Changes | Default when the brief is silent |
|---|---|---|
| Target | seams, symmetry, UDIM vs sets, triangulation | game engine, one 0-1 set per material |
| Texturing method | seam count and visibility | 3D painting/Substance: seams may show at the back |
| Camera | density and priorities | third person 512 px/m, closest view defines tiles |
| Texture budget | resolution, sets, UDIM count | 2048 per set; texture_size_for decides |
| Deformation | seams off joints, tangent-space normals | character: tangent space, unique UVs |
| High source | Selected to Active vs Multires | separate sculpt: Selected to Active |
| Engine convention | normal green channel | OpenGL (Blender, Unity, glTF); directx=True for Unreal |
Workflow
- Pre-flight. Apply scale (On Mars 3D: it "fixes the vast majority" of problems). Clear non-manifold geometry, doubles and back faces (SpeedChar). Decide on Mirror and Solidify modifiers. If UVs exist, compare
island_topologywithuv_qaislands: seam flags in files are often leftover retopology marks, so runseams_from_uv_islands(Lampel) before re-unwrapping. GATE: scale 1,bx_auditclean, topology table read. - Seams. Hard surface:
seams_from_sharp(obj, 30, mark_sharp=True). Organic: build part labels (head, ears, limbs, torso front and back, soles, inner mouth and eyelids per the Kaspar and Morren seam maps inreferences/expert-notes.md), thenseams_from_groups(obj, labels). Specific cuts:seam_path(obj, v_a, v_b, view_dirs). Thencut_to_disks(obj, view_dirs=[camera]). GATE:must_cutempty; a checker render shows no seams across hero areas that the brief cannot afford. - Unwrap.
unwrap(obj, method="BEST"), or the method the island type calls for. GATE:unsolved_faces0 (the operator returns FINISHED even when it fails),folding_islandsempty (elseseams_from_groups(..., faces=folding_faces)and unwrap again), angle p95 at most 15 to 20 deg. - Straighten grid-like islands (hard surface, garments, strands):
straighten(obj)before any scaling. GATE: straight stripes in the checker. - Texel density.
normalize_texel_density(obj, target, res, weights), thentexture_size_for(obj, target). GATE: island spread within 10-20%, or the intended priority ratios. - Pack.
pack(obj, tex_res=res), withscale=Falsefor a strict density andallow_any=Truefor organic islands. UDIM: pack each region, thenmove_to_tile. GATE:uv_qa: 0 overlaps, gap at least res/128, border at least gap/2, coverage at least 0.70 or an explained exception. - Look.
uv_layout_image,checker_review,distortion_review(metric="angle"). Judge withreferences/critique.md. - Bake prep.
prepare_low(low, triangulate=game, sharp_from_uv_islands=hard_surface). Check the silhouette against the high (bx_audit.audit(low, high=...)). Explode interpenetrating or moving parts (explode, one axis). Runmeasure_projection:miss_pctunder 2 andwrong_part_pctunder 1, else use a cage, explode, or bake per pair. - Bake.
bake_high_to_low(low, highs, out, res=512)first, then the final resolution. It uses adaptive extrusion by default and hides every other object from render. Multires:bake_multires. Extras:bake_id,curvature_from_normal. GATE:sanity[...]["problems"]empty (inverted texels under 2%, notes from 0.2%), 16-bit PNG. If inverted texels remain, runmap_problem_reviewto see where. Ifsweep_projectionshows they do not move with extrusion, fix the geometry (fit, holes, thin parts), not the numbers. - Review and hook up.
bake_review(low, highs, paths, out): the low with maps must read like the high under the raking light. Thenhookup_maps. Report measured numbers and what was not verified.
Numbers
| Value | Relative to | Source |
|---|---|---|
| 10.24 / 20.48 / 5.12 / 2.56 px/cm | FPS world / FPS weapon / third person / top-down | Huge Menace [00:12:28] |
| 10-20% density mismatch invisible | between islands or sets | Gambrell [01:36:16] |
| coverage above 70%, 75% studio minimum | one 0-1 tile | Gambrell [01:31:56], Huge Menace [00:16:27] |
| island gap res/128 (8/16/32 px at 1K/2K/4K) | texture resolution | Lampel [00:38:01], On Mars 3D [00:11:18] |
| FRACTION margin m: gap 2m, border m | Blender pack | verified |
| bake margin res/128, 16 px at 2K | texture resolution | On Mars 3D, digest |
| cylinder sides: 6 min, 8 recommended | low poly silhouette | On Mars 3D [00:03:48] |
| samples: 16 for normals, 128 for AO, 1 only while iterating | antialiasing and noise | Gambrell/Marmoset 16, SpeedChar 4x4, verified |
| test bake 512, final 2048 | iteration loop | SpeedChar [00:59:09] |
| AO distance about 0.2 x bbox diagonal (default 10 units) | asset size | [added], verified effect |
| angle error p95 at most 15 deg organic, 3 deg flat | per corner | [added] from 5.2.1 measurements |
| Blender packer on an organic character: 55-62% | 16 px gap at 2K | measured on the sheep (several runs) |
| inverted texels: single pass 7.3-7.8%, adaptive 2.2% (sculpt) / 1.2% (consistent high) | selected to active, sheep | measured |
| 2048 bake, 186k-face high: one pass normal + AO 4.4 s, adaptive 25 s | Metal GPU, headless | measured |
Quality gates
Code: q = uv_qa(obj, res), then uv_verdict(q, "game" or "film", priorities=...). unwrap()["unsolved_faces"] and ["folding_islands"]. measure_projection(). bake_high_to_low()["sanity"] (inverted, exact-flat, non-unit, AO black and white). Cross-check overlaps with bpy.ops.uv.select_overlap() when in doubt; it agreed with the raster on the tests.
Visual: layout PNG (islands, magenta overlaps), color-grid checker from front, side, three-quarter and the production camera (squares square, letters upright, seams hidden), angle and density heat maps, bake_review sheet (high, low, low + maps), map_problem_review when sanity flags pixels.
Common mistakes
| Mistake | Looks like | Fix |
|---|---|---|
uv.unwrap() without method | Conformal area distortion | pass method= or use unwrap(method="BEST") |
| Trusting FINISHED | closed island keeps its old UVs, repacked | zero the UVs first, check for zero-area faces (unwrap() does it) |
| Re-unwrapping without Seams from Islands | layout lost; retopology marks used as seams | seams_from_uv_islands first |
| Cutting every island to a disk | slits, folded islands | measured cut_to_disks (default) |
| Pack rotation ANY after straightening | tilted grids, jaggy lines | CARDINAL |
| Blender margin 0.005 | padding depends on island scale | FRACTION, gap/(2*res) |
| Target node active but not selected | bake returns CANCELED, no error | select and activate; assert FINISHED |
| Low carries the sculpt as Multires | almost flat normal map | render levels 0 for the bake (bake_high_to_low does it) |
| max_ray shorter than extrusion | 90% exactly flat map | ray = extrusion + inward depth |
| One global extrusion on thin, close parts (ears on a head) | inverted patches, neighbors baked in | adaptive bake (default), explode, or per-pair bakes |
| Other objects render-visible during AO | black AO | hide them from render; the active low never occludes |
| Default AO distance 10 | AO all dark or flat | about 0.2 x asset diagonal |
| Stacked UVs baked in 0-1 | one side's detail on both | unique UVs or offset_stacked |
| Hard edge inside an island | crack lines | seam it, or prepare_low(sharp_from_uv_islands=True) |
| Editing UVs after the bake | seams, tangent mismatch | never; re-bake |
Blender 5.2 notes
uv.unwrapdefaults to CONFORMAL. On failure it keeps the island's old UVs and repacks them. It hasuse_weights/weight_group(default group "uv_importance"): importance weights with Minimum Stretch. A weighted top half went from 31% to 74% of UV area [verified].- Sync select is on by default (5.0).
BMLoopUVonly hasuvandpin_uv. Ame.uv_layers[...]reference goes stale after an Edit Mode toggle, so keep the name. pack_islands(udim_source="CLOSEST_UDIM")headless packed a region sitting in 1002 back into 1001: pack first, then move.uv.move_on_axisneeds a UV editor.- Bake: the target must be selected AND active (5.0). Operator defaults differ from scene defaults (
use_clearFalse vs True, EXTEND vs ADJACENT_FACES): pass everything. Selected to Active uses the low's render evaluation (Multires render_levels, shape keys). Multires settings live inscene.render.bake.use_multireswithobject.bake_image(). A float image saved as PNG is 16-bit. Normal Map nodeconventionOPENGL/DIRECTX (5.1). UDIM tiles needimage.tile_add(fill=True). - Auto Smooth is gone (4.1): smooth shading plus sharp edges; Triangulate "Keep Normals" is
keep_custom_normals.
References
references/expert-notes.md: judgment by expert with timestamps, disagreements and deciding conditions, my 5.2.1 measurements. Load when choosing seams, density or a bake strategy.references/procedures.md: tested pipelines (hard-surface, character, film/UDIM, Multires, bake-fix loop) and raw bpy snippets executed by the tests. Load before writing code.references/critique.md: rubric for seams, stretch, density, packing, bake setup and baked maps, with proxies and severity. Load at every gate.references/sources.md: videos, credentials, URLs, best timestamps, web references.scripts/bx_uvbake.py: the module (docstrings list every function).
Signals
- GitHub stars
- 681
- Forks
- 82
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
scenario-blender-uv-baking- Source
- github.com/scenario-labs/skills