Files
Papay-Shooter/.claude/skills/character-pipeline/references/stylization.md
T
Nicholas ButzkeandClaude Opus 5 cd0d1b2d99 feat(characters): import the Quaternius mannequin as a selectable skin
A third playable character, built with the pipeline skill from a source
that was already in the repo: the animation library ships a rigged
Mannequin mesh on the exact 53-joint reference skeleton, CC0, so it needed
no download and retargets perfectly. 18 clips, 0.3% cross-leg bleed, 7% of
verts at four influences — a clean authored-weight import. Licence
recorded in mannequin.license.json as the other skins do.

It has no cloth chains, correctly: it is a mannequin and has neither hair
nor clothes.

Importing it turned up two real bugs, both of which would have hit any
flat-coloured or single-piece model:

- LevelMaterials.apply_character_look treated ANY untextured surface on a
  character as the model's own outline shell and hid it, so the mannequin
  rendered as a solid black silhouette — its body and joint materials are
  untextured flat colours, not ink. _is_line_work() now asks whether the
  surface is named eyes*, is drawn front-face-culled (the inverted-hull
  setup), or is near-black. Taila and Miku are unaffected: their materials
  are textured and never reach that branch. Verified by render.

- verify_character.py failed the build for having one mesh. That check
  cannot tell "the pipeline joined them" from "the artist authored one
  mesh" — Quaternius' mannequin is one piece on purpose. It is advisory
  now; the join path's two unambiguous signatures, cross-leg bleed and the
  4-influences-everywhere spread, are still hard checks.

Also restored Miku's description, which the re-import had blanked.

3 GLB skins selectable (6 with the built-in colour skins). Smoke 0
failures, 11/11 movement tests, cloth idle 0.024-0.078 deg/frame.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-26 13:25:26 -04:00

86 lines
4.2 KiB
Markdown

# Stylization — the cel-shaded look
Two passes, applied at load in `SkinnedPlayerModel.load_model`:
```gdscript
LevelMaterials.apply_toon_recursive(scene) # world-wide toon shading
LevelMaterials.apply_character_look(scene) # character-only corrections
```
## The trap: imported models bring their own line-work
Anime models exported from MMD/VRoid/Blender toon setups very often ship the
outline **as geometry** — an inverted-hull shell of the mesh with a flat black,
UNTEXTURED material, plus separate flat cards for the eye whites, irises and the
pupil highlight. The mesh you import is not just the character; part of it is
already the drawing.
Toon-lighting that shell is what put a **white rim on every hair strand**. It is
an inverted hull whose normals face away from you; a lighting model that adds a
rim term lights it brightly exactly where it is supposed to read as ink.
`apply_character_look` therefore looks for the model's own line-work and handles
it flat and unshaded. "Untextured" alone is NOT the test — that made every
flat-coloured model render as a black silhouette, because Quaternius' mannequin
has two untextured materials (a yellow body, lilac joints) and both were hidden
as though they were an outline shell. `_is_line_work()` asks three things
instead: is it named `eyes*`, is it drawn front-face-culled (the classic
inverted-hull setup), or is its albedo near-black. An ink shell is black; a
flat-coloured character is any colour at all.
What it then does:
- **Outline hull** → made fully transparent rather than deleted. Deleting a
surface would renumber the rest and break the mesh's own skin bindings. The
game draws its own outline.
- **Eye cards** (`resource_name` starts with `eyes`) → flat ink, except anything
with `HL` in the name, which is the glint in the pupil and really is white.
If a newly imported character comes out with a white halo, a black silhouette,
or black eyes that should have irises, `_is_line_work()` and the name-matching
below it are where to look. The conventions vary by source and this is the one
place they are read.
Taila's eyes still render as black cards rather than amber irises. Her eye
surfaces are untextured, and the glTF import hands every untextured surface a
default near-white albedo, so colour cannot tell an iris card from a lash card
on her — the name is all there is, and `eyes*` currently means "ink". Unfixed.
## Materials on import: the unlit problem
Anime glTFs are very often exported "unlit": `KHR_materials_unlit`, a **black**
`baseColorFactor`, and the real texture wired to `emissiveTexture`. Renderers
honouring the unlit extension use base colour and ignore emission — so Blender
reads black, never references the images, and imports with `bpy.data.images`
**empty**. The character comes out a silhouette, and there is no node graph left
to patch afterwards.
`tools/gltf_fix.py` rewrites the container **before** import: emissive becomes
base colour, the unlit flag is dropped. It must run first — this is the first
thing `retarget.py::main` does, before `import_any`.
`fix_unlit_materials(meshes)` then repairs anything left inside Blender.
## What the toon pass does
`apply_toon_recursive` gives everything the game's banded ramp. `apply_character_look`
then softens the banding on characters, because re-banding an already-shaded
anime texture reads as gloss — the texture already contains its own shading and
the second pass fights it.
## Convention alignment
The Hoyoverse-class look is, broadly: a ramp texture indexed by NdotL for the
body, a separate ramp and often a dedicated shader for the face, an inverted-hull
outline whose width is vertex-colour-modulated, and specific handling for eyes
and hair highlights. This project does the simplified version — one banded ramp
plus a screen-space-ish ink treatment, and the model's own outline shell hidden
in favour of the game's. The face is NOT specially shaded here; if a character
comes out with harsh shadow shapes across the nose, that is the missing piece.
## Outline thickness
Lives with the toon material in `scenes/maps/level_materials.gd`
(`CHARACTER_INK` and the outline settings). This is the branch it was last
touched on — `feat/outline-thickness-and-tp-weapon-hold`.