Nodes/BrainDead Nodes/BD UV Transfer
ComfyUI Node

BD UV Transfer

Warp a face texture onto a whole different rig's UV layout

By BizaNator·Created 8 months ago·Updated 3 days ago· 15
BD UV Transfer
  • source_texture
  • source_mask
  • uv_texture
  • filled_mask
  • status
correspondence_path
output_size2048

Here's the FaceWrap problem this node solves: you've built a beautiful UV texture for a face using your own canonical UV layout - every view blended, every gap filled - and now the actual rig you're shipping to uses a different UV layout (a CC5 head, a Metahuman, some third-party rig). A texture is just pixels arranged in UV space; move it to a new layout and it's garbage unless you warp it correctly. BD UV Transfer is the warp. It retargets a UV texture from one mesh's UV layout to another's using a precomputed vertex-correspondence file, and it does it with GPU rasterization so the result is per-texel accurate, not a sloppy approximation.

The key idea that makes it practical: you build the correspondence once per target rig and reuse it for every head. That's what tools/build_correspondence.py in the pack is for - run it on your target rig, save the .npz, and then every face you ever paint can be transferred to that rig in one node.

How it works

The node loads the .npz correspondence, then for each texel in the target's UV layout it figures out which point in the source texture that texel should sample, and bilinearly samples it. The clever part is validity: the correspondence carries a valid_mask (target vertices that had no good source correspondence - the scalp or rear of a head when the source was face-only), and texels whose triangles straddle the boundary get dropped rather than painted with garbage. Wire in the optional source_mask (your source filled_mask from the blend step) and target texels mapping to unfilled source regions come out empty too.

The outputs are uv_texture (the warped result at output_size, default 2048, up to 8192), filled_mask (where the new texture actually has data), and a status string reporting coverage percentage - the number to watch, because a low coverage means your correspondence was too sparse for that head.

Inputs: source_texture, correspondence_path (the .npz), output_size, optional source_mask.

The dependencies you need to know about

Two hard requirements, both visible in the code:

  • nvdiffrast - the GPU rasterizer. If it's not installed, the node returns an empty texture and a status saying exactly that. pip install nvdiffrast (or let the pack's environment provide it).
  • CUDA - it refuses to run without a CUDA device; no CPU fallback here. The node literally returns "ERROR: CUDA required" otherwise.

Installation

Pack install is the usual:

cd ComfyUI/custom_nodes
git clone https://github.com/BizaNator/ComfyUI-BrainDead
cd ComfyUI-BrainDead
pip install -r requirements.txt

(or ComfyUI Manager → "BrainDead"), then make sure nvdiffrast is present in your ComfyUI venv.

Common issues

  • Empty output + "nvdiffrast not installed" - self-explanatory. Install nvdiffrast.
  • "correspondence file not found" - the path you gave doesn't exist. It's a path string, not a picker - double-check you're passing the full path to your .npz, built with tools/build_correspondence.py.
  • Low coverage percentage - the correspondence was built for a different target, or the source mask cuts off regions the target wants. Rebuild the correspondence for the actual rig you're shipping to.
  • Soft blurry edges - the node deliberately drops partial-validity boundary triangles to avoid garbage seams; a little edge loss there is by design, and it's why the workflow feeds the result into a Qwen Image Edit finalize pass next.
Category🧠BrainDead/FaceWrap

Inputs (4)

NameTypeDefaultDescription
source_textureIMAGESource UV texture (e.g. from BD_UVConfidenceBlend).
correspondence_pathSTRINGPath to .npz built by tools/build_correspondence.py.
output_sizeINT2048256–8192Target-UV texture resolution.
source_maskoptMASKOptional: source filled_mask. If provided, target texels mapping to unfilled source regions become 0 in the output mask.

Outputs (3)

NameTypeDescription
uv_textureIMAGE
filled_maskMASK
statusSTRING