Body Decal Surface Warp
Displacement and depth warping for decals
- decal_layer
- decal_mask
- depth_map
- displacement_map
- region_mask
- clip_mask
- warped_decal_layer
- warped_decal_mask
Body Decal Surface Warp is the "conform it to the surface" stage of the modular pipeline. A decal that's been perspective-warped still sits on the body; this node makes it follow the surface - muscle definition, clothing wrinkles, fabric folds, the contour of a limb - by pushing its pixels around with a displacement map and a depth map. It's the difference between a tattoo that looks painted on a mannequin and one that looks printed on skin.
You feed it the outputs of Body Decal Place (decal_layer, decal_mask), optionally add maps, and get back a warped layer and mask for Body Decal Composite. It's also a great place to understand what the all-in-one Body Decal Apply is doing internally - that node has the same depth_* and displace_* inputs folded in.
How it works
The mechanism is a reverse-mapped, bilinear-sampled warp: for each output pixel it reads the displacement map, computes offset = map_value − neutral, and samples the decal from a source position shifted by strength_x/strength_y times that offset. What that means in practice:
- Neutral gray (
depth_neutral/displace_neutral, default 0.5) means no shift. Areas of the map brighter than neutral push pixels one way, darker push the other. Thestrength_*values scale how far - and they can go negative to flip direction. - A map that's all 0.5 does nothing. A map at 0 or 1 is a constant-shift disaster. You want something with local variation - that's what the wrinkles and muscle ridges encode.
Depth is applied first, then displacement. The two are conceptually similar but sourced differently: depth_map typically comes from a depth preprocessor (Depth Anything, MiDaS - the KB's depth essay is the rundown on those) and captures scene-level contour; displacement_map is usually a texture-derived map, like the output of this pack's own Image To Displacement Map node, and captures surface detail.
Inputs
Required are decal_layer and decal_mask plus the strength/neutral pairs. The optional inputs are where the maps come in: depth_map, displacement_map, region_mask, and clip_mask. region_mask multiplies into the decal's alpha (so warping only visibly applies where the region was), and clip_mask does the same from the other direction - handy for keeping a decal off the edges of the body.
Outputs are warped_decal_layer and warped_decal_mask, ready for Body Decal Composite.
Gotchas
Here's the trap that catches everyone: the sliders have defaults of displace_strength_y = 8 and the rest at 0, but with no map fed in, the node is a no-op pass-through. Cranking the strength sliders does nothing unless a displacement_map or depth_map is actually wired in - the code bails out early when a map is missing. Also, displacement maps need a sane neutral value or the whole decal shifts sideways rather than conforming; 0.5 is right for the maps Image To Displacement Map produces, and if your decal visibly slides, check that displace_neutral matches the map's midpoint.
Install: ComfyUI Manager → "ComfyUI Body Decal", or git clone https://github.com/verias/comfyui-body-decal.git into ComfyUI/custom_nodes and restart. Deps are just numpy and Pillow; the depth maps come from preprocessor packs you'll already have for ControlNet work.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| decal_layer | IMAGE | — | |
| decal_mask | MASK | — | |
| depth_strength_x | FLOAT | 0.0-100–100 | — |
| depth_strength_y | FLOAT | 0.0-100–100 | — |
| depth_neutral | FLOAT | 0.500–1 | — |
| displace_strength_x | FLOAT | 0.0-100–100 | — |
| displace_strength_y | FLOAT | 8.0-100–100 | — |
| displace_neutral | FLOAT | 0.500–1 | — |
| depth_mapopt | IMAGE | — | |
| displacement_mapopt | IMAGE | — | |
| region_maskopt | MASK | — | |
| clip_maskopt | MASK | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| warped_decal_layer | IMAGE | — |
| warped_decal_mask | MASK | — |