UltraTex Prep (mesh + reference)
The boring node that decides whether your texture turns out
- reference_image
- reference_mask
- trimesh
- prep
- normal_views
- reference
Everyone stares at the sampler. The sampler is not where UltraTex runs fail. This node is.
UltraTex is a 2K multi-view texture model - you give it a bare mesh and one reference photo, it paints six 2048² views of the object, and those get baked back onto the mesh as a real UV texture you can open in Blender or drop into a game engine. That is a genuinely different job from image-to-3D: you already have the geometry, and you want it textured properly instead of accepting the smeared approximations a shape model invents for the sides it never saw. If you've read the community's long-standing complaint that generated meshes arrive with "messed-up UV unwrapping," this is the pipeline built to sit after that step rather than repeat it.
But a diffusion model can't paint a mesh. It paints pictures of a mesh from fixed cameras. So somebody has to pick the cameras, unwrap the UVs, render the geometry as normal maps the model can condition on, and line your reference photo up with the front view. That's UltraTex Prep, and there is no way around it.
What it actually does
Three jobs in one node, in this order:
- Mesh normalisation. The mesh is merged to a single object, centred, and scaled to a bounding radius of 0.5, because the cameras are fixed at distance 1.67 with a 35.5° field of view. Normals get sanitised - NaN and zero vertex normals are rebuilt from area-weighted face normals, and vertices that still have none inherit their nearest valid neighbour's. That quietly rescues a lot of wonky exports.
- UV unwrap at
uv_mode, and a render of the six canonical TexVerse views (world-space normals in the RGB, coverage in the alpha). - Reference alignment. Your image's alpha is cropped out and fitted onto the front view's silhouette, then flattened onto white.
The inputs you'll actually touch
uv_mode is the one that decides how long you wait. comfy_gpu (default) calls ComfyUI's own GPU unwrapper - the same one behind the core Unwrap Mesh UVs node - and takes seconds on a 500k-face mesh. xatlas is the CPU fallback at roughly 4.6 minutes for the same mesh. keep_existing trusts the UVs already on your file, which is the right call if you've hand-unwrapped something and want the bake to land in your layout. If those UVs are missing or the count doesn't match, it logs a warning and falls back to the GPU unwrapper rather than failing.
reference_image plus the mask wiring is the other trap. If your image has alpha, that alpha is the foreground. If it doesn't, you must connect reference_mask - the node raises an error telling you exactly that. And mask_meaning is genuinely easy to get backwards: ComfyUI's Load Image MASK is 1 on transparent pixels, while most background-removal nodes output 1 on the object. Pick the wrong one and the model gets a nicely illuminated background with your subject punched out of it.
mesh_path and the optional trimesh input are alternatives - the wire wins. The path accepts anything trimesh can read (glb, obj, ply…), absolute or relative to the input folder.
Outputs
prep is the real payload: geometry, UVs, the six normal G-buffers and the aligned reference, all in one object. It goes into UltraTex Sampler for generation and into UltraTex Bake Texture for the back-projection, and it has to be the same prep in both or the bake won't line up.
normal_views and reference are diagnostics, and you should use them. Look at normal_views before you spend eight minutes sampling: if a limb is black, or the object is off-frame, or you've got the whole thing upside down, you'll see it here in a second instead of after the sampler. reference tells you whether the alignment landed where you think it did.
Install
Manager → search ComfyUI-UltraTex, or:
cd ComfyUI/custom_nodes
git clone https://github.com/visualbruno/ComfyUI-UltraTex
Dependencies are trimesh, moderngl, xatlas and scipy. The pack draws with moderngl, so it wants a working GL context - a headless box with no GL drivers is the classic failure here rather than anything about the model.
Where people get burned
- A very old ComfyUI has no GPU unwrapper, so it silently falls back to xatlas and your prep goes from seconds to minutes. The log line says so.
- Prep is cached by ComfyUI once it has run. Rerunning the sampler with a new seed won't re-unwrap, which is what you want, and also why changing the mesh doesn't visibly change anything until you re-trigger the node.
- Nothing here is resolution-free.
render_sizedefaults to 4096 because that's what TexVerse renders are; if you plan to sample at 1024, droppingrender_sizeto match is fine and faster.atlas_sizeis the UV packing resolution the charts are laid out for - keep it at or above the bake'stexture_sizeor you're paying for texture space you never filled.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| reference_image | IMAGE | — | |
| mesh_path | STRING | Mesh file (glb/obj/ply/...), absolute or relative to the input folder. Ignored when a TRIMESH is connected. | |
| render_size | INT | 4096512–4096 | TexVerse renders are 4096 px, then resized to the sampling resolution. |
| atlas_size | INT | 4096512–8192 | Texture resolution the xatlas charts are packed for. |
| uv_mode | COMBO | comfy_gpu | comfy_gpu: ComfyUI's GPU unwrapper (same as the 'Unwrap Mesh UVs' node), seconds instead of minutes. xatlas: CPU, slow on dense meshes. keep_existing: the mesh's own UVs. |
| reference_alignment | COMBO | fit_front_silhouette: crop the reference to its mask and fit it on the front view's silhouette. | |
| mask_meaning | COMBO | LoadImage's MASK is 1 on transparent pixels; background-removal nodes usually output 1 on the object. | |
| reference_maskopt | MASK | — | |
| trimeshopt | TRIMESH | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| prep | ULTRATEX_PREP | — |
| normal_views | IMAGE | — |
| reference | IMAGE | — |