1 — Name the materials
In Blender, give each recolourable zone a material whose name starts with
tintable_ (e.g. tintable_1, tintable_2). Any other material name renders fixed.
How an item gets its colour. There are two layers, and they stack:
.glb — this is what renders. ✅ Availabletint_channels
defaults; there is no in-game player recolour yet. ⚠️ PartialBake the item’s texture into the .glb when you export it from Blender (built-in
File → Export → glTF 2.0 (.glb), “Include → Images: Automatic”). There is no separate texture file
and no models/item PNG for the worn mesh — the renderer reads the texture straight out of the GLB.
A textured GLB now renders that texture; a tinted one renders texture × tint.
If every part of the item is one fixed look, you’re done after baking the texture. Just omit
tint_channels — with no channels declared, the mesh renders exactly as authored.
(supports_color is irrelevant here either way — see the caution below.)
{ "type": "tiedup:bondage_item", "display_name": "Ropes", "model": "tiedup:models/gltf/binds/ropes.glb", "regions": ["ARMS"]}Tint lets you recolour named zones at render time without touching the mesh — a ball gag’s leather
strap can stay black while the ball is tinted red. The colour is the per-channel default you declare
in the item JSON (one colour per channel, the same for every stack of that item — there is no
per-stack player recolour today). It works by multiplying the baked texture by that colour:
finalPixel = texture × tint.
1 — Name the materials
In Blender, give each recolourable zone a material whose name starts with
tintable_ (e.g. tintable_1, tintable_2). Any other material name renders fixed.
2 — Author grayscale
Paint the tintable zones in grayscale. The tint colour replaces the hue, so a grayscale
base reads correctly as texture × tint. A coloured base would multiply twice and look muddy.
The material name in the GLB is the channel name, and it must match the key you use in the item JSON.
| Material name | Behaviour |
|---|---|
tintable_1, tintable_2, … | Tintable zone — one independent recolour channel per name |
anything else (strap, buckle, metal) | Fixed, renders as baked |
Numbers don’t have to be sequential — tintable_1 and tintable_5 is fine; the number is just part
of the name. Example for a ball gag:
| Material | Texture | Behaviour |
|---|---|---|
strap | leather colour | fixed |
tintable_1 | grayscale ball surface | recolourable |
buckle | chrome | fixed |
Use luminance-only (grayscale) textures for tintable zones. A mid-gray base (~128) gives more natural results than pure white: dark pixels stay dark whatever the tint, which preserves shadows and crevices.
| Gray input | Tint | Result |
|---|---|---|
| White (255) | Red (255, 0, 0) | bright red |
| Mid-gray (128) | Red (255, 0, 0) | dark red |
| Black (0) | Red (255, 0, 0) | black (shadow preserved) |
Declare each channel’s colour under tint_channels. Keys match the tintable_ material names from the
GLB; values are hex, with or without a leading #. tint_channels is the only field that drives
bondage tinting — the render path reads it directly.
{ "type": "tiedup:bondage_item", "display_name": "Ball Gag", "model": "tiedup:models/gltf/gags/ball_gag.glb", "regions": ["MOUTH"], "tint_channels": { "tintable_1": "#FF0000" }}The values in tint_channels are read per item at render time. There is no player recolour UI today —
the tint_channels defaults you declare are what you always see. ❌ Planned
tintable_N materials in Blender..glb (built-in glTF 2.0 exporter).tint_channels defaults in the item JSON, with each key matching a tintable_
material name. (Don’t bother with supports_color — it’s inert on the bondage path.)/reload and check the item in-game — confirm the texture shows and each channel tints the
right zone.