Nodes/ComfyUI-GLB-Face-Retexture/GLB Face Retexture (Preserve Geometry)
ComfyUI Node

GLB Face Retexture (Preserve Geometry)

Your image-to-3D face is the tell — this pastes the real one back on

By KaletoAI·Created 7 days ago·Updated a day ago· 1
GLB Face Retexture (Preserve Geometry)
  • source_glb
  • reference_image
  • corrected_glb
  • comparison
  • proof_metrics
◄strength1.00►
◄feather0.18►
◄render_resolution768►
◄yaw0►
◄light_balance0.00►
◄tone_match0.00►
◄brightness1.00►
◄skin_recolor0.00►

Every image-to-3D model in the current crop has the same weak spot. The silhouette and shoulders look great, you orbit round to the face, and it's a waxwork - soft, slightly wrong, often shifted red because the texture model drifted off the skin tone of the photo you fed it. Topology and rigging are a bigger, separate fight. The face you can fix today.

What it does, and what it won't

GLBFaceRetexture takes a GLB you already generated plus the same front-on photo and projects that photo onto the mesh's visible face texture. No regeneration, no mesh surgery, no re-rigging.

The README is honest about it: geometry is preserved, but the output is not a byte-identical GLB. Your triangles get partitioned into a body primitive and a face primitive, and a dedicated face material/atlas is appended. Vertex positions, UVs, normals and the original embedded image bytes stay untouched. It's a texture-layer edit, not a remesh.

Which means planar photo projection, not 3D face reconstruction: it can't improve a silhouette, invent missing geometry or fix anatomy the generator baked in, and mismatched perspective or expression can still show.

How it works

A CPU-only worker (worker.py) runs MediaPipe in a private Python 3.12 venv, sealed off from ComfyUI's torch environment. It renders the mesh's front albedo view at render_resolution, finds a face with the FaceLandmarker, re-renders the head crop at native resolution, and finds the face in your photo the same way. A selfie segmentation model masks both sides - skin in, hair out - so the blend region is a face oval, not a rectangle of hair roots.

Registration fits photo landmarks onto render landmarks, with a residual gate that bails if the fit is off by more than 30% of the eye span. That's what saves you from a silently smeared face. Then it rasterises the face triangles, projects the photo through the UVs, feathers the edge and paints a fresh atlas.

The controls worth touching

source_glb has to be a native FILE_3D with format == "glb" - wire it from Load3DAdvanced, not a file path. reference_image takes exactly one RGB image; a batch is rejected outright. yaw is the first thing to reach for when detection fails on the mesh: it's the viewing angle used for registration, and the asset should sit Y-up, +Z front. strength and feather (1.0 / 0.18) are blend strength and mask softness - leave them alone until registration is good.

Then the optional controls, which are why this isn't twenty lines of numpy:

  • light_balance (0 = off) removes the photo's broad shading - side light, the darker cheek where the face turns away - so the texture carries skin colour instead of a baked shadow that reads as a seam at the projection edge. Features survive; nothing is mirrored.
  • tone_match pulls the photo's median face colour toward the mesh's own face skin so the projection blends in without a seam.
  • brightness (1.0 = unchanged) is the manual trim when those two don't sit right.
  • skin_recolor recolours the original mesh skin - every skin-coloured texel, face, neck and hands included - toward the photo's tone: the fix for a red-shifted generator. Only chroma moves and each texel keeps its luminance, so highlights can't blow out. Hair and clothes are safe, though it can't tell a skin-coloured shirt from skin.

Defaults are no-ops, so a bare run gives you the raw projection.

What comes out

corrected_glb goes straight into Preview3DAdvanced and SaveGLB. comparison is a before/after IMAGE plus the adjusted photo - send it to PreviewImage and actually look, because that's where bad registration or an over-recoloured neck shows up. proof_metrics is JSON (also shown as node text): output_triangle_count, protected_body_changed_render_pixels (measured from the atlases of the file that was written, not the in-memory intent), and factor_clipped_texture_pixels.

Install

Manager's Install via Git URL works, but it won't prepare the worker, and neither does a plain clone. Use the actual ComfyUI interpreter:

cd ComfyUI/custom_nodes/ComfyUI-GLB-Face-Retexture
../../venv/bin/python install.py

Windows portable needs the three-parent step (the embedded interpreter sits beside ComfyUI, not inside it):

..\..\..\python_embeded\python.exe install.py

That builds .venv, installs requirements-worker.txt (MediaPipe, NumPy 1.26, SciPy, Pillow, OpenCV) and downloads two hash-checked Google models - face_landmarker.task and selfie_multiclass.tflite - into the pack's models/, refusing to touch ComfyUI's environment. You need Python 3.12 or uv on the machine (--python / --uv if it can't find them). Restart ComfyUI and hard-refresh the browser.

Where people get burned

Nearly every failure is the worker, not the node. "Private CPU worker unavailable" or a missing-model error means the installer didn't finish - and the plain installer won't fix a broken .venv, so use install.py --recreate. A rigged GLB, external textures or an already-processed output fails loudly: it wants one static, single-primitive mesh with an embedded uncompressed opaque texture, so always start from the original GLB. Long jobs hit an 1800-second timeout; lower render_resolution before touching GLB_FACE_RETEXTURE_TIMEOUT. The README also notes a Windows ComfyUI GUI session is still unverified - if the node won't even load, check the native Load3DAdvanced/SaveGLB/FILE_3D plumbing first.

Category3d/texture

Inputs (10)

NameTypeDefaultDescription
source_glbFILE_3DOriginal static textured Trellis GLB. Geometry is preserved, not regenerated.
reference_imageIMAGEOne matching RGB face photo. Face detection and registration run anew.
strengthFLOAT1.000.01–1—
featherFLOAT0.180–0.5—
render_resolutionINT768256–2048—
yawFLOAT0-180–180Source viewing yaw in degrees for face registration.
light_balanceoptFLOAT0.000–1Removes the photo's broad shading from the face (side light, darker face edges where the cheeks turn away) so the texture carries skin colour, not light. Features are kept and nothing is mirrored. 0 = off.
tone_matchoptFLOAT0.000–1Pulls the photo face's overall skin colour and brightness toward the mesh's own face skin, so the projection blends into the surrounding texture without a seam. 0 = off.
brightnessoptFLOAT1.000.5–1.5Final brightness scale of the photo face before projection. 1 = unchanged.
skin_recoloroptFLOAT0.000–1Recolours the mesh's own skin (face, neck, arms, hands — every skin-coloured texel of the original texture) toward the photo's skin tone, for meshes whose generator shifted the skin colour. Clothes and hair are left alone. 0 = off.

Outputs (3)

NameTypeDescription
corrected_glbFILE_3D—
comparisonIMAGE—
proof_metricsSTRING—