Grooming

The groom is the source. Cards are a bake.

A groom is resolution-independent and restylable. Cards are a disposable engine-facing derivative,
re-baked when the budget changes, when an LOD tier is added, or when the tool changes. Everything on
this page follows from that one sentence, including which folder each file goes in.

Doing rather than reading?

Make a Hairstyle walks the whole route in order. This page is the reference behind it:
the file format, the import, and the normal transfer.


Where the files live

Hair uses two of the three stages of the Raw / Source / Exported mirror. Raw is for
unprocessed captures and a groom is not one.

Source: the .sir scene, its guides, its density and clumping The thing you open, edit and keep. Under Meshes/Character/Hair/, beside the .blend.
Exported: the card mesh, the atlas maps Generated. Never hand-edited, and re-written whole by the next bake.
Editing cards instead of the groom kills the groom

Nudge a card in Blender and it is correct until the next re-bake, which overwrites it. So the fix
is either lost, or kept by never re-baking. Then the groom is decoration and every future
budget change is manual work on a few hundred quads.

Nothing checks this. The card mesh cannot say whether the groom it came from still describes
it. A card that is wrong is fixed in Sisir and baked again.


The route

OBJ

atlas maps

add-on FBX

Sisir

Blender

Unity

Sisir makes the groom, the cards and the atlas. Blender bakes the channel data, transfers the
normals and runs the checks. Unity gets an .fbx and a texture set, and nothing else.


Sisir

Grooming is guide-based: you style a small number of guide curves, and the strands interpolate
between them. That is what makes a groom restylable. A parting is moved by moving four guides, not
by touching ten thousand hairs.

Cards come out of the same groom. Sisir clusters the strands, fits a strip to each cluster and maps
it into an atlas strip. The groom stays; the cards are its output.

It writes OBJ for the cards, and the atlas maps as ordinary image files.


Why OBJ and not Alembic

We need exactly three things out of Sisir, and OBJ carries all three:

Positions v lines.
Atlas UVs vt lines. Which strip a card sits in is the only thing about the texture the mesh has to remember.
Separable cards A card is a connected component of the mesh, so the faces alone separate them. OBJ carries the faces, so this costs nothing.

Alembic's advantage over OBJ is arbitrary geometry parameters and animation, and we want neither.

We do not want the parameters because we bake the channels ourselves. A per-strand random or a
width that Sisir wrote is a number whose definition lives in Sisir's release notes. Our contract is
arc length along this card, a hash of the quantised root centroid, and a stiffness somebody
decided, and Sisir computes none of those three. Carrying its params across would give us a second
set of numbers that look like ours, mean something else, and disagree silently.
→ Hair Cards

We do not want the animation because there is none to carry. The cards ship static and the movement
happens in the shader, from the channels. → Hair Motion

What is left is a format every tool writes, that opens in a text editor when an import goes wrong.


What OBJ costs, and the three answers

OBJ is unit-less and declares no up-axis. A file that is metres and Z-up is byte-identical to one
that is centimetres and Y-up. That is a real cost and it is answered three times, because one answer
would not hold.

  1. The import operator pins the convention. Import a groom through the add-on, not through
    File ▸ Import, which remembers whatever the last person set. It reads the file as metres and
    Y up (what most tools write) and lands it the studio's way, Z up. Scale and Up Axis
    are on the operator, in public, for the groom that arrives from somewhere else. The forward axis
    is not. It follows the up axis (-Z with Y up, Y with Z up), so either lands the file
    unrotated. → Bubbly Games/Workflows/3D Models/3D Models > Scale and orientation
  2. MSH022 measures the groom against a person and against the body. A groom imported at a
    hundred times scale looks perfectly ordinary on its own, so the whole card set is measured: its
    longest side must be at least a centimetre and no longer than the character is tall. When a
    power of ten would fix it, the finding says which: probably centimetres, import it again with
    the scale multiplied by 0.01
    . It is also measured against the mesh named Body beside it
    (SM_Character_Body_01, never a hat or a proxy with another name). A groom whose cards sit further
    from the box round the body than a card is long, and more than 5 cm, is not on the character
    (an up axis swapped, or never moved onto it) and is reported as such. When the Body stands up
    along Z, the box is its head (the top 15% of it), so a groom left on the chest is caught
    too. A Body in a T-pose, or a flat proxy, has no top slice that is a head, and is measured by
    its whole box instead.
    → Hair Cards > What the checks can and cannot see
  3. MSH002 catches the wrong fix. The natural repair for a bad import is to scale the object
    until it looks right, which bakes a factor into the .fbx and hands it to everything downstream.
    MSH002 requireAppliedScale is already an error, so that export is blocked rather than
    shipped. → Modular 3D Assets > What blocks an export

The third is the one worth knowing. The first two stop a bad import; the third stops the bad import
being papered over.

Off the character, the flip check does not run

Which end of a card is its root is measured from the Body. A groom metres from the body would
get a verdict that means nothing, and a Flip button beside it that would turn good UVs upside
down. So when the groom is off the character, the flip check says it did not run rather than
guessing. Placement asks about the nearer end of each card, whichever end that is, so a
groom whose atlas is simply upside down is still on the character and still gets its flip
verdict.


Normal transfer from a scalp proxy

Cards lit by their own flat normals read as a pile of planes. Each card catches light as the
quad it is, the ones facing the light go bright together, and the silhouette the alpha works so hard
for is contradicted by the shading behind it.

Blend the normals toward a scalp proxy (a smooth cap over the skull) and the same cards read
as one volume. The geometry does not change. Only what it tells the light does.

This is the biggest look win on the page for the least work, and it is a Blender step, so the
import does it on the way in when you name the Scalp Proxy in its sidebar. It runs on the joined mesh,
or on every object in turn when Join is off or the join fails. There is nothing in the cards that
says which way the skull under them faces, so with no proxy named the import says it skipped the
transfer rather than guessing one.

Transfer Hair Normals does the same job later, from the Hair Cards panel. It opens a dialog: pick
the proxy from a searchable list of the scene's objects, set Blend, and tick Apply Now only
when the look is settled. A live modifier can still follow a re-shaped scalp, and applying can't be
undone. The field opens on the proxy the cards already borrow from, and empty otherwise. It
never opens on the last groom's scalp or on a guess at the Body, because a wrong guess looks right until
the head is lit from the side, and an empty field just asks.

Pressing it again updates the transfer rather than stacking a second, because two transfers from two
proxies blend into a look nobody chose. It finds the existing transfer (a Data Transfer modifier carrying custom normals) even if you renamed
it, switches it back on if it was off, and keeps anything else it was set up to transfer.

The proxy is never counted as cards, whatever it is called: naming it here is saying it is the
scalp. It is only measured from (by the flip and placement checks) if its name says Body.
→ Hair > Which mesh is the cards, and which is the Body

requireNormalTransfer is on in the rules for the same reason: it is cheap, it is invisible when it
is missing until someone lights the head, and by then the cards are already in a prefab. MSH022
checks it on the mesh that ships, so a live transfer counts on a machine that exports with
modifiers and not on one that exports without them. When the cards ship flat, the finding names the
one repair that applies, because each sends you somewhere different:

The finding says Do
There is no transfer Run Transfer Hair Normals
The transfer names no source object Pick the proxy in it, or run the button again
The transfer is switched off in the viewport Switch it on. The export takes the viewport's result
This machine exports without modifiers Apply the transfer, or export with modifiers on
It is on, it is exported, and still nothing Check that it transfers Custom Normals from the proxy

None of them has a Fix button: which proxy the normals come from is a look decision, and the whole
body is the wrong answer for hair that hangs past it.

The file's own normals are dropped on import, on purpose

Blender imports an OBJ's normals as custom normals, and a file's normals are the flat card
normals the transfer exists to replace. Left in, they would pass the check that asks whether the
cards ship transferred normals, while lighting as exactly the pile of planes the check is for. So
the import clears them first. A groom brought in through File ▸ Import keeps them, and passes
that check wrongly. One more reason to import through the add-on.


Export through the add-on

Always export through our add-on, never Blender's own FBX exporter. This is the existing house
rule and hair does not get an exception. → Bubbly Games/Workflows/3D Models/3D Models > Import and export

It runs the checks first, and it knows where the file belongs. MSH022 is among them, and like
every new rule it lands as a warning. It reports a flipped card, a card over two atlas strips
and a budget overrun, and it does not block. → Check Your Work > When a rule is wrong

Hair has a second reason. Only the add-on's export ships the attribute flagged for render. Unity
reads only the first colour set, and Blender's own exporter does not control which one that is. The
add-on puts the render-flagged attribute first for the export and leaves your mesh unchanged.
→ Hair Cards > The render flag decides which attribute ships

Overdraw is not measured on export. Press Measure Hair Overdraw before exporting.
→ Hair > Overdraw is an average, and it is measured on request


See also