3D_Projection
This is where your model actually gets painted
- image
- mesh_model
- model_model
- albedo
Every node in ComfyUI-Paint3D-Nodes is in service of this one moment: the sampled images you just generated get pasted onto the actual 3D mesh. 3D_Projection is the node that turns a flat SD1.5 render into a real texture map on your obj file. If you've been staring at the workflow and wondering where the "paint" happens - it happens here.
What it does
The whole Paint3D trick is incremental. First you render depth maps of the untextured mesh, use them as a depth ControlNet to sample front/back views, and now you have two images of what the model should look like. But images aren't textures. This node takes that stitched 2-view image, splits it back into its two halves, renders the mesh from the matching camera angles (invisible to you, driven by the same cam1/cam2 camera indices), and projectively paints each view onto the mesh's UV texture. The projection is exactly the pipeline from the Paint3D paper this pack wraps.
It reads the mesh file path from the TRAINCONFIG you loaded earlier - it doesn't ask you where the obj lives again. It also writes everything into a new Paint3D/ folder sitting next to your obj file, so you can watch the texture accumulate pass by pass.
Inputs that matter
- image - the stitched 2×1 image out of your KSampler (via VAEDecode). The node splits it in half internally, so the left half gets projected at
cam1and the right half atcam2. - mesh_model - the
MESHMODELfrom 3D_LoadMeshModel. Same one every stage shares. - cam1 / cam2 - integer camera indices, 0–26, default 0 and 23. These must match the cameras you used to generate the depth image and the sample, or the paint lands in the wrong place. In the bundled workflow, front is 0 and back is 23.
Outputs
- model_model (a
MESHMODEL) - the mesh with its texture updated in memory. Wire this forward into the inpaint stages. - albedo (an
IMAGE) - the exported texture map so far. "Albedo" is the texture-without-lighting, which is the point: the lightless map is what you hand to the final UV cleanup.
Installing this pack
Manager is the easy route: search ComfyUI-Paint3D-Nodes in ComfyUI Manager and install. Or do it by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/N3rd00d/ComfyUI-Paint3D-Nodes
pip install -r requirements.txt
pip install kaolin==0.15.0 -f https://nvidia-kaolin.s3.us-east-2.amazonaws.com/torch-2.1.0_cu121.html
The kaolin line is the one that'll fight you - it's NVIDIA's 3D deep learning library and it's picky about torch. The README's own fallback is to pin torch 2.1.0 + cu121 (pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --extra-index-url https://download.pytorch.org/whl/cu121) if your existing torch version doesn't match the kaolin wheel. You also need the depth and inpaint ControlNets from comfyanonymous/ControlNet-v1-1_fp16_safetensors plus the UV Pos ControlNet from GeorgeQi/Paint3d_UVPos_Control - the workflow loads sd15/control_v11f1p_sd15_depth_fp16.safetensors, control_v11p_sd15_inpaint_fp16.safetensors and sd15/Paint3d_UVPos_Control.bin from your models/controlnet folder. Only SD1.5 works; the UV Pos ControlNet is SD1.5-only.
Gotchas
The biggest beginner trap: rerun the full chain from the top after changing cam1/cam2 in one node. Depth image, sample, and projection all take camera indices, and they have to agree. Also note the node stores intermediate results in that Paint3D/ folder - if your obj is read-only or the folder path is weird, that's where it'll fail. Don't expect much community help either: this pack has essentially zero Reddit footprint, so the README and the paper are your docs. It's niche, it's fiddly, and for "texture an arbitrary mesh with SD" there isn't really a friendlier option.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| mesh_model | MESHMODEL | — | |
| cam1 | INT | 00–26 | — |
| cam2 | INT | 230–26 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| model_model | MESHMODEL | — |
| albedo | IMAGE | — |