UV unwrapping and baking

SkillDev tools

Use 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.

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_disks test-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_distance counts 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

InputChangesDefault when the brief is silent
Targetseams, symmetry, UDIM vs sets, triangulationgame engine, one 0-1 set per material
Texturing methodseam count and visibility3D painting/Substance: seams may show at the back
Cameradensity and prioritiesthird person 512 px/m, closest view defines tiles
Texture budgetresolution, sets, UDIM count2048 per set; texture_size_for decides
Deformationseams off joints, tangent-space normalscharacter: tangent space, unique UVs
High sourceSelected to Active vs Multiresseparate sculpt: Selected to Active
Engine conventionnormal green channelOpenGL (Blender, Unity, glTF); directx=True for Unreal

Workflow

  1. 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_topology with uv_qa islands: seam flags in files are often leftover retopology marks, so run seams_from_uv_islands (Lampel) before re-unwrapping. GATE: scale 1, bx_audit clean, topology table read.
  2. 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 in references/expert-notes.md), then seams_from_groups(obj, labels). Specific cuts: seam_path(obj, v_a, v_b, view_dirs). Then cut_to_disks(obj, view_dirs=[camera]). GATE: must_cut empty; a checker render shows no seams across hero areas that the brief cannot afford.
  3. Unwrap. unwrap(obj, method="BEST"), or the method the island type calls for. GATE: unsolved_faces 0 (the operator returns FINISHED even when it fails), folding_islands empty (else seams_from_groups(..., faces=folding_faces) and unwrap again), angle p95 at most 15 to 20 deg.
  4. Straighten grid-like islands (hard surface, garments, strands): straighten(obj) before any scaling. GATE: straight stripes in the checker.
  5. Texel density. normalize_texel_density(obj, target, res, weights), then texture_size_for(obj, target). GATE: island spread within 10-20%, or the intended priority ratios.
  6. Pack. pack(obj, tex_res=res), with scale=False for a strict density and allow_any=True for organic islands. UDIM: pack each region, then move_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.
  7. Look. uv_layout_image, checker_review, distortion_review(metric="angle"). Judge with references/critique.md.
  8. 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). Run measure_projection: miss_pct under 2 and wrong_part_pct under 1, else use a cage, explode, or bake per pair.
  9. 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, run map_problem_review to see where. If sweep_projection shows they do not move with extrusion, fix the geometry (fit, holes, thin parts), not the numbers.
  10. Review and hook up. bake_review(low, highs, paths, out): the low with maps must read like the high under the raking light. Then hookup_maps. Report measured numbers and what was not verified.

Numbers

ValueRelative toSource
10.24 / 20.48 / 5.12 / 2.56 px/cmFPS world / FPS weapon / third person / top-downHuge Menace [00:12:28]
10-20% density mismatch invisiblebetween islands or setsGambrell [01:36:16]
coverage above 70%, 75% studio minimumone 0-1 tileGambrell [01:31:56], Huge Menace [00:16:27]
island gap res/128 (8/16/32 px at 1K/2K/4K)texture resolutionLampel [00:38:01], On Mars 3D [00:11:18]
FRACTION margin m: gap 2m, border mBlender packverified
bake margin res/128, 16 px at 2Ktexture resolutionOn Mars 3D, digest
cylinder sides: 6 min, 8 recommendedlow poly silhouetteOn Mars 3D [00:03:48]
samples: 16 for normals, 128 for AO, 1 only while iteratingantialiasing and noiseGambrell/Marmoset 16, SpeedChar 4x4, verified
test bake 512, final 2048iteration loopSpeedChar [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 flatper corner[added] from 5.2.1 measurements
Blender packer on an organic character: 55-62%16 px gap at 2Kmeasured on the sheep (several runs)
inverted texels: single pass 7.3-7.8%, adaptive 2.2% (sculpt) / 1.2% (consistent high)selected to active, sheepmeasured
2048 bake, 186k-face high: one pass normal + AO 4.4 s, adaptive 25 sMetal GPU, headlessmeasured

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

MistakeLooks likeFix
uv.unwrap() without methodConformal area distortionpass method= or use unwrap(method="BEST")
Trusting FINISHEDclosed island keeps its old UVs, repackedzero the UVs first, check for zero-area faces (unwrap() does it)
Re-unwrapping without Seams from Islandslayout lost; retopology marks used as seamsseams_from_uv_islands first
Cutting every island to a diskslits, folded islandsmeasured cut_to_disks (default)
Pack rotation ANY after straighteningtilted grids, jaggy linesCARDINAL
Blender margin 0.005padding depends on island scaleFRACTION, gap/(2*res)
Target node active but not selectedbake returns CANCELED, no errorselect and activate; assert FINISHED
Low carries the sculpt as Multiresalmost flat normal maprender levels 0 for the bake (bake_high_to_low does it)
max_ray shorter than extrusion90% exactly flat mapray = extrusion + inward depth
One global extrusion on thin, close parts (ears on a head)inverted patches, neighbors baked inadaptive bake (default), explode, or per-pair bakes
Other objects render-visible during AOblack AOhide them from render; the active low never occludes
Default AO distance 10AO all dark or flatabout 0.2 x asset diagonal
Stacked UVs baked in 0-1one side's detail on bothunique UVs or offset_stacked
Hard edge inside an islandcrack linesseam it, or prepare_low(sharp_from_uv_islands=True)
Editing UVs after the bakeseams, tangent mismatchnever; re-bake

Blender 5.2 notes

  • uv.unwrap defaults to CONFORMAL. On failure it keeps the island's old UVs and repacks them. It has use_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). BMLoopUV only has uv and pin_uv. A me.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_axis needs a UV editor.
  • Bake: the target must be selected AND active (5.0). Operator defaults differ from scene defaults (use_clear False 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 in scene.render.bake.use_multires with object.bake_image(). A float image saved as PNG is 16-bit. Normal Map node convention OPENGL/DIRECTX (5.1). UDIM tiles need image.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