Hair Cards
A card is a flat strip of geometry carrying a clump of hair in its alpha. Our cards are generated
in Sisir from the groom, so a card is a bake of the groom, never the thing itself. In Blender the
add-on gives every card three numbers per vertex that the engine reads.
This page is that contract. Get a channel wrong and nothing looks broken: the hair hangs correctly,
lights correctly and passes every mesh rule we already have. It goes wrong the first time it moves,
in the engine, on someone else's machine.
Make a Hairstyle is the pipeline, groom to atlas to engine. This is the contract behind
it, and the page to open when a MSH022 finding does not make sense.
A card is a mesh loose part
A card is a connected piece of the mesh (a loose part), not a UV island. Every number on this
page is worked out per loose part, and every finding names one. Cards share atlas strips on purpose,
so fifty cards can sit in exactly the same spot in UV space and still be fifty cards.
Two cards welded at one vertex are still two cards. Sisir does that routinely, and each card is
measured on its own. The opposite case is the one that gets reported: one card exported as two
halves sitting on top of each other without sharing a vertex. Each half looks like a complete card,
so each one animates as a whole strand from its own middle.
The channel contract
Cards carry a FLOAT_COLOR attribute named HairData on the POINT domain. Four channels, three
of them used:
| Carries | Range | Domain | |
|---|---|---|---|
| R | Root → tip along this card | 0 at the scalp, 1 at the tip |
Per vertex |
| G | Per-card random, stable across re-bakes | 0–1 |
Flat across the card |
| B | Stiffness: how much this card may move | 1 pinned, 0 free |
Flat across the card |
| A | Reserved for later | - | - |
The Domain column is the part that usually breaks. Only R varies along a card. G and B are
one value on every vertex of a card, and a G that varies within a card is reported.
The generated values behind this table (the attribute, the domain, the colour space and the card
budget) are on Hair > The budget.
MSH022 is the check, and it is a warning like every new rule.
→ Check Your Work > When a rule is wrong
Why R exists when V already runs up the strip
A short card only uses the top part of its atlas strip. V says where the card is in the
texture; R says where a vertex is along this card. Movement, length and growth all work
per card, so reading them off V would make every short card behave like a long one that has been
cut off.
Why R is measured along the card
R follows the card's spine, not a straight line from root to tip. On a curled card the straight
line is much shorter than the card, so the middle of a ringlet would read as 0.8 instead of 0.5.
Curly cards are also the ones that move most, so this matters.
Why G comes from the root position, not the card number
Card numbers change whenever a card is added or deleted. If G came from the number, deleting one
card from a fringe would give every later card a new G and shift the motion of the whole fringe on
the next bake. G is worked out from where the card's root sits, so it stays the same through
re-bakes, re-ordering and neighbouring cards being deleted.
Why B is stiffness and not length
Length can be measured from the geometry. Stiffness is a decision somebody makes: a braid that
should not swing is an artistic choice, and nothing in the mesh says so. Using a channel for
something that can be calculated would waste it.
So the bake leaves B alone. It writes 1 (pinned) on every card once, when it creates the
attribute, and never again unless Reset Stiffness is ticked. Set stiffness with Mark Hair
Stiffness in the Hair Cards panel: in Edit Mode, select any part of the cards that should not swing
and press it, and every card the selection touches is set as a whole. Pressed in Object Mode, it sets
the whole groom.
Which colour field is the contract
Blender stores a colour attribute in two fields (sRGB and linear), and the two show different numbers.
Only one of them reaches Unity.
The FBX exporter writes the sRGB field, so those are the numbers the contract is about:
0.5 in R means halfway from root to tip, with nothing to convert on either side.
The catch is in Blender's display. Its colour picker applies a curve to these values, so a vertex
exactly halfway up shows a different number there, and a straight ramp looks bent in the viewport.
Use the channel overlay (overlay_values) to read these channels. It shows the actual number.
Alpha is not colour-managed, so it is the same in both fields. Whatever ends up in A later reaches
the engine unchanged.
The render flag decides which attribute ships
A mesh can carry several colour attributes and the FBX carries all of them, but Unity only reads the
first one. Blender's own exporter does not use the render flag (the camera icon in the colour
attribute list) to decide the order, so another colour can end up first by accident.
The add-on's export puts the render-flagged attribute first for the export, and leaves your mesh as it
was afterwards. In practice:
- Bake Hair Cards flags
HairDatafor render unless another attribute already has the flag.
If one does,MSH022reports it, and its Flag fix moves the flag. - Export through the add-on. Blender's own FBX export ships whichever colour it meets first.
→ Grooming > Export through the add-on
Re-baking also never changes the stiffness you set. The add-on writes values in a way that keeps B
exactly as it was, however many times you bake.
The atlas
A hair atlas is to a character what a trim sheet is to a kit: one sheet the whole set maps into,
named for the set and not for any one piece. That is the same sharedPerSet logic TEX007 already
applies, for the same reason: one material, one draw call, and strips that are painted once and used
by everything that needs them.
There is one difference:
| A trim sheet tiles | A band repeats along U for as long as the piece is, so requireSeamlessTilingAxis is true and a seam in the wrong place shows as a repeat down a corridor. |
| A hair card does not | A card maps into its strip once, root at V = 1, tip wherever the card ends. requireSeamlessTilingAxis is false on hairAtlas, because a hair strip is never meant to tile. |
It is T_Cutout_ by the texture ladder, at step 5: does alpha cut its silhouette? A card without
its alpha is a rectangle. It does not stop at step 4 as T_Trim_, because that step is about one
sheet a whole kit maps bands of, and a hair atlas is neither.
The atlas is UV channel 0 (the first UV layer, the one FBX writes first and Unity samples), not
whichever layer is active in Blender. The bake, Flip and the check all read channel 0, so
clicking a different UV layer to look at it changes nothing.
rootAt in the rules says which end of the strip the root is at. Every bake, fix and check in the
add-on reads the same value, so they always agree on which way a card runs.
TEX009 yetThe block above is the layout, written down. No tool checks a sheet against it: not the
add-on, not the Asset Editor, not Workspace Check. A hair atlas with the wrong prefix, one sheet per
hairstyle, or a missing map passes without a warning. Two of its keys are used by MSH022:
rootAt by every bake, and strips (where a project sets one) by the straddle check. Until
something reports the rest, whoever reviews the atlas has to check it.
What the checks can and cannot see
| Finding | What it means | What it offers |
|---|---|---|
| A card carries no hair data | Never baked, or baked before it existed | Bake · Select |
R does not span 0→1 on a card |
Baked as part of a bigger part, or split after the bake | Bake · Select |
R does not keep rising along the card |
The card is folded, or its spine was traced wrong | Bake · Select |
G is not flat within a card |
Painted over after the bake, or two cards joined into one | Bake · Select |
R or G is from an older bake |
The card was reshaped after its bake, or its data was copied from another card | Bake · Select |
Two cards share a G |
Their roots are so close that they are two cards on top of each other. X cards are fine: two planes crossed on one centre line are left alone. A card duplicated in place is still reported, because its plane is parallel, not crossed | Select |
| A card sits on another without sharing a vertex | Usually one card exported as two halves, each animating as a whole strand | Select |
B is not flat within a card |
Half a braid pinned and half free | Select |
| The root end is further from the Body than the tip | The card is flipped | Select · Flip |
| A card straddles two atlas strips | It samples a neighbouring clump part-way up | Select |
| The bake could not describe a card | The panel says why: a seam inside it, UVs laid sideways, too small | Select |
| The data is stored as bytes | Only 256 levels, for a channel that needs more precision | Convert |
| The data is not the attribute flagged to render | Another colour ships ahead of it, and Unity reads only the first → The render flag decides which attribute ships | Flag |
| The card set is not the size of hair | Imported in the wrong unit. The finding names the unit when a power of ten fixes it | - |
| The groom is not on the character | An up axis swapped, or never moved onto the body. The flip check does not run | - |
| A modifier changes the card count | Usually a Mirror on a lash set. Card findings on that mesh are held back | - |
| The mesh has no UV layer | Nothing says which end of a card is the root. Import it again with its atlas UVs | - |
| A region word sits in the descriptor slot | SM_Character_Beard_01 is not checked as hair until it is named SM_Character_Beard_<Style>_01 |
- |
| The cards ship without transferred normals | They light as a pile of planes. The finding names which of five things is wrong → Grooming > Normal transfer from a scalp proxy | - |
| Card count over budget · overdraw over target on average | The numbers, and why overdraw is the one that matters, are in Hair > The budget | - |
Things worth knowing when you read these:
- "From an older bake" catches what no shape check can: a card that was lengthened after baking
still has anRfrom0to1, and a card with copied data has a perfectly flatGthat belongs
to its neighbour. Re-bake to fix both. - The straddle check is off until you set Atlas strips in the Hair Cards panel. The panel says
so instead of showing a clean result. Setting it to0falls back to the project's default, if the
rules set one. - A card the bake could not describe is listed once with the reason, and left out of every other
row. Fix the card first (usually its UVs), then bake. If the data is stored as bytes, press
Convert before baking. - Leave Edit Mode or Sculpt Mode to get results. A mesh being edited is not measured, and the
panel says measured when you leave Edit Mode. - Apply modifiers that add or remove cards, then bake. Until then the card findings on that mesh
are held back, because the cards you see are not the cards the fixes would write to. - Placement is measured from the Body's head when the Body stands upright (the top 15% of it).
The nearer end of each card has to be within 5 cm of that box, which catches a groom left on the
chest. A Body in a T-pose or a flat proxy cap is measured by its whole box instead. - A card counts as flipped when its root end is further from the Body than its tip by more than a
quarter of its length (or 1 cm on very short cards). A card lying flat along the skin is not judged
either way.
What the buttons do, and the one that is not in Fix All
A bold entry in that last column is a fix with only one right answer. It appears beside the finding in
Issues and runs in Fix All. R and G are calculated, so the fix for them is always a bake, and
a bake never touches B.
Select selects the named cards in Edit Mode, so card 14 of 96 becomes hair on the screen. It
refuses if the number of cards has changed since the check ran, because card numbers shift when cards
are added or deleted. If you have just edited the cards, let the panel update before pressing it. It
also refuses on hidden objects and excluded collections.
Flip turns a flipped card the right way up (its atlas UVs mirrored top to bottom inside its own
strip, then baked again). It is not in Fix All, because it relies on the Body being the right
mesh, and if it isn't, Flip would turn good cards upside down. Check the finding, then press it
yourself.
A flipped card hangs and lights correctly and sits exactly where you put it, but it animates
from the tip. You won't see it in Blender or in a screenshot, only once the character turns
their head in the game.
Two things the checks can't judge:
- Whether
Bis right. The check can sayBis flat across each card, not whether a braid
should have been pinned. - Whether the hair looks good. Silhouette, clumping and where the parting falls are up to you and
your reviewer.
The count and overdraw rows are the only ones about what the hair costs. The numbers live in
Hair > The budget, and overdraw only shows up once somebody has pressed Measure Hair Overdraw.
See also
- Hair: the three tiers, and which one a hairstyle is
- Hair Motion: what reads
R,GandB, and which half of that is written down - Grooming: the Sisir groom these cards are generated from, and why it is the source
- Make a Hairstyle: the steps, in order
- Characters · Modular 3D Assets: the reference pages either side of this one
- Asset Rules (Generated) > Hair: the words for the parts of a card
- Check Your Work > When a rule is wrong:
MSH022is a warning, like every new rule