π SuperPBR Material
One photo in, albedo and ORM out, no sampler involved
- image
- model
- vae
- clip
- mask
- anchor_image
- tilesig
- albedo
- orm
You have a photo of an object, or a render that looks like one, and you want to relight it in Blender. What you actually need is the material, not the picture: base colour, roughness, metalness, a bit of AO. Normally that's Substance Painter and a lost weekend. π SuperPBR Material does it in one pass and gives you back two images - albedo and orm.
This is the inverse of what the 3D generation packs do. Hunyuan3D 2.1 and TRELLIS.2 made their name by shipping PBR channels alongside the mesh, because a relightable asset is worth ten baked diffuse maps. SuperPBR goes the other direction: no mesh, no multi-view, no per-face UV atlas. One 2D image of one object, decomposed into physically-plausible maps you can plug into a shader.
What's actually happening in that pass
SuperMat (ICCV 2025) is a Stable Diffusion 2.1 UNet finetuned into a single-step feed-forward network. The pack doesn't port a pipeline - it loads the checkpoint into ComfyUI's own UNetModel and does something sneaky: the final three up blocks are replicated into two heads, one decoding albedo and one decoding ORM. The rest of the diffusion machinery is bypassed. Your image is VAE-encoded (posterior mode, not a sample, so it's deterministic), fed in with zeroed latents at a pinned timestep of 999, and the two heads are decoded by the same VAE.
Why one pass works: with zeroed latents, SD 2.1's v-prediction target collapses to scale Γ clean_signal, which the code computes once as a constant. So there's no noise, no steps, no CFG, no seed and no sampler node anywhere in the graph. Rare case of "single-step" meaning it.
The inputs you'll actually touch
Required are image, model, vae, resolution and batch_chunk.
vae has to be a 4-latent-channel SD VAE - vae-ft-mse-840000-ema-pruned.safetensors is the one the author points at. Wire an SDXL or Flux VAE and you get a clean, honest error rather than silent garbage, but it's still a wasted run. model comes from Load SuperPBR Model. resolution defaults to 512, which is what SuperMat trained at, and it bilinearly resizes your image to a square - so feed it a crop of a single object, not a full scene with a hundred things in it.
Optional, in order of usefulness:
mask- follows LoadImage's convention, 0 = subject, 1 = background, and the node composites background to flat 0.5 gray. SuperMat expects objects on mid-gray, so connect your matte if your background isn't already flat.clip- an SD 2.1 text encoder, used only for the fixed empty prompt. Skip it. Without it the node substitutes a zero context: a negligible shift in the output, and you save ~675 MB of VRAM plus the encoder load. It is the emptiest prompt in ComfyUI; don't pay for it.anchor_imageandtilesig- the tile-drift fix, both from Tile Split. Connect both and the node makes one extra global pass over the whole downscaled image and blends each tile onto it with a frequency split in pixel space, so low frequencies (colour and tone) come from the global pass and high frequencies from the tile. Tile-to-tile drift dies. Note it forces the working resolution to the tile size.batch_chunk(default 8) - images per UNet/VAE pass. Same output at any value; lower it when a big tile grid OOMs.
Outputs are albedo (IMAGE, [0,1]) and orm. The ORM is packed: R = ambient occlusion, G = roughness, B = metallic. Export it as a packed ORM for a Blender/Unity-style material, or run it through ORM Split to get three separate grays.
Install
ComfyUI Manager, search the pack title, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Hullabalo/ComfyUI-SuperPBR.git
Then drop supermat.pth anywhere under ComfyUI/models/diffusion_models/. Restart.
Worth saying out loud: this pack has no dependencies. pyproject.toml declares an empty list, there's no requirements.txt, and it rides on ComfyUI's own torch. In a hobby where every node pack is a dependency-conflict coin flip, that's a feature.
Where it goes wrong
The model dropdown listing nothing means your checkpoint isn't under models/diffusion_models. If you pick the wrong file from that list - it shows every .pth and .safetensors there - the loader raises ... is not a Base SuperMat checkpoint and helpfully prints the missing and unexpected keys. That's a wrong-checkpoint message, not a broken install.
Then the two visual failures. A washed-out object with a glowing halo around it usually means no mask on a non-flat background. And if your subject comes out looking like it was resized badly, it was: resolution squashes anything non-square, so crop first or go through Tile Split.
Genuinely useful shortcut: the repo ships example graphs in workflows/ - SuperPBR SuperMat basic.json and a MoGe3 variant. Load one and you skip half this article. There's essentially no community chatter on this pack yet, so those graphs are the documentation.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | β | |
| model | SUPERMAT | β | |
| vae | VAE | β | |
| resolution | INT | 512256β2048 | β |
| batch_chunk | INT | 81β16 | Images decomposed per UNet/VAE pass; larger batches are processed in chunks of this size and concatenated back β same output, capped peak VRAM. Lower it if you OOM on big tile grids. |
| clipopt | CLIP | SD 2.1 text encoder, only used for the fixed empty-prompt context. Optional; a zero context (negligible output shift) is used when not connected. | |
| maskopt | MASK | Follows LoadImage's MASK convention: 0 = subject (kept), 1 = background (composited to 0.5 gray). | |
| anchor_imageopt | IMAGE | Anchor from Tile Split's anchor_image output (mask-composited, padded to the tile canvas). It is decomposed once and its low frequencies anchor every tile, killing tile-to-tile colour drift. | |
| tilesigopt | TILESIG | Grid signature from Tile Split; needed to crop the anchor on the tile grid. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| albedo | IMAGE | SuperMat albedo map in [0,1]. |
| orm | IMAGE | ORM map: R=occlusion (AO), G=roughness, B=metallic. Split into separate maps with the ORM Split node. |